Last active
September 20, 2016 21:54
-
-
Save donabrams/2d6bd01dbe2809377f92b2e1fcec0fb1 to your computer and use it in GitHub Desktop.
React/Redux w/ Immutable.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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