Skip to content

Instantly share code, notes, and snippets.

@donabrams
Last active September 20, 2016 21:54
Show Gist options
  • Save donabrams/2d6bd01dbe2809377f92b2e1fcec0fb1 to your computer and use it in GitHub Desktop.
Save donabrams/2d6bd01dbe2809377f92b2e1fcec0fb1 to your computer and use it in GitHub Desktop.
React/Redux w/ Immutable.js
import { Map, is } from 'immutable'
import { createStore, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { pure } from 'recompose'
// ACTIONS
const INCREMENT_ACTION = "INCREMENT_ACTION"
const LOAD_AUTHOR = "LOAD_AUTHOR"
const increment = () => ({type: INCREMENT_ACTION})
const loadAuthor = (id, data) => ({type: LOAD_AUTHOR, id, ...data})
// REDUCERS
// counter reducer
const initialCounterState = Map({ count: 0 })
const counterReducer = (action, state = initialCounterState) => {
switch(action.type) {
case INCREMENT_ACTION: {
return state.set("count", state.get("count") + 1)
}
default: {
return state
}
}
}
// counter accessor
const getCount = ({counter}) => counter.get("count")
// author reducer
const initialAuthorState = Map().merge({1: {name: 'Don', imgSrc: 'https://avatars0.githubusercontent.com/u/447032?v=3&s=40'}})
const authorReducer = (action, state = initialAuthorState) => {
switch(action.type) {
case LOAD_AUTHOR: {
const { type, id, ...rest } = action
// We don't want to update the state if nothing changed, so use Immutable.is() to compare it.
// Sadly there's no Immutable.js method to do this... yet
const newState = Map(rest)
return is(state.get(id), newState) ? state : state.set(id, newState)
// The time/memory cost of the above Map construction and is() call is expensive and may be optimizable
}
default: {
return state
}
}
}
// author accessors
const getAuthor = ({author}, id) => author.get(id)
const getName = (author) => author.get("name")
const getImgSrc = (author) => author.get("imgSrc")
// Counter View
// This is a "pure" function now, but not optimized by React
const Counter = pure(({count, increment}) => <div onClick={increment}>{count}</div>)
const SmartCounter = connect(
// creating a new object every render; should update only on count change
(state)=>({count: getCount(state)}),
// Once again, creating a new object every render...
(dispatch)=>bindActionCreators({increment}, dispatch)
)(Counter)
// Author view
const Author = pure(({name, imgUrl}) => <div>{name}<img src={imgUrl}/></div>)
const SmartAuthor = connect((state, {id})=> {
const author = getAuthor(state, id)
// creating a new object again... every render; should update only on author change
return { name: getName(author), imgSrc: getImgSrc(author) }
})(Author)
// Main App component
const App = <div><SmartCounter /><SmartAuthor id={1} /></div>
// index.js
const store = createStore({counter: counterReducer, author: authorReducer})
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElement('app')
)
// Same as ImmutableEx1 except for below
import { mapProps } from 'recompose'
// slightly modified helpers
const getCounter = ({counter}) => counter
const getCount = (counter) => counter.get("count")
// Counter View
const SmartCounter = compose(
connect(
// creating a new object every render; should update only on count change
(state)=>{counter: getCounter(state)},
// Once again, creating a new object every render...
(dispatch)=>bindActionCreators({increment}, dispatch)
),
pure, // here we decide to update if counter Immutable object is ever changed
mapProps(({counter})=>({count: getCount(counter)}))
// The above is rather wordy and the intentions are not clear without comments... a funny smell
)(Counter)
// Author view
// Same "optimizations" as above
const SmartAuthor = compose(
connect((state, {id})=>({author: getAuthor(state, id) })),
pure,
mapProps(({author})=>({ name: getName(author), imgSrc: getImgSrc(author) }))
)(Author)
// Same as first example except where overridden
// This one uses redux-immutable so redux store is fully immutable
import { combineReducers } from 'redux-immutable'
// counter accessor
const getCounter = (state) => state.get("counter")
const getCount = (counter) => counter.get("count")
// author accessors
const getAuthor = (state, id) => state.getIn(["author", id])
const getName = (author) => author.get("name")
const getImgSrc = (author) => author.get("imgSrc")
const SmartCounter = connect(
// creating a new object on every render; should update only on counter change
(state)=>({count: getCount(getCounter(state))}),
(dispatch)=>bindActionCreators({increment}, dispatch)
)(Counter)
// Author view
const SmartAuthor = connect((state, {id})=> {
const author = getAuthor(state, id)
// creating a new object again... every render; should update only on author change
return { name: getName(author), imgSrc: getImgSrc(author) }
})(Author)
// index.js
const store = createStore(combineReducers({counter: counterReducer, author: authorReducer}), Immutable.Map())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment