Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Practical Guide to Using ImmutableJS with Redux and React

In a previous blog, I went into the pros and cons of using ImmutableJS with React and Redux. Now, I’ll attempt to go through a basic example of using everything together and go into more advanced topics further down. Even if you have an existing codebase, you can start adding immutable parts of your state tree one piece at a time.

import { Record, List } from 'immutable';// First create a record definition
const myRecord = Record({
loading: true,
aNumber: 0,
aList: List(),
});
// Now create a record instance
const initialState = myRecord({
loading: true,
aNumber: 0,
aList: List(),
});
// file myReducerAndActions.jsimport { Record, List } from 'immutable';
import { handleActions } from 'redux-actions';
const myRecord = Record({
loading: true,
aNumber: 0,
aList: List(),
});
// Since the defaults are the same as the set values, we could
// just not pass in an object and get the same result
// i.e., const initialState = myRecord();
const initialState = myRecord({
loading: true,
aNumber: 0,
aList: List(),
});
const actions = {}; // to be filled in laterexport default handleActions(actions, initialState);
// file mainStore.jsimport {
createStore,
combineReducers,
applyMiddleware,
} from 'redux';
import myReducer from './myReducerAndActions.js';
import createLogger from 'redux-logger';
import _mapValues from 'lodash/mapValues';
import thunk from 'redux-thunk';
let middlewares = [thunk];

if (process.env.NODE_ENV !== 'production'
&& process.env.TEST !== 'true'
) {
const logger = createLogger({
collapsed: true,
stateTransformer: state =>
// Not all the reducers are Immutable structures so have
// to check if they are for each one.
// This function could get very costly over time, but it's
// super useful for debugging.
_mapValues(state, (reducer) => {
if (reducer.toJS) {
return reducer.toJS();
}

return reducer;
}),
});

middlewares = [...middlewares, logger];
}

const rootReducer = combineReducers({
myReducer,
});

const store = createStore(
rootReducer,
applyMiddleware(...middlewares),
);

export default store;
// file myReducerAndActions.jsimport { Record, List } from 'immutable';
import { handleActions, createAction } from 'redux-actions';
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST';
const fetchDataRequest = createAction(FETCH_DATA_REQUEST);
const FETCH_DATA_RESPONSE = 'FETCH_DATA_RESPONSE';
const fetchDataResponse = createAction(FETCH_DATA_RESPONSE);
const fetchData = () =>
(dispatch) => {
dispatch(fetchDataRequest());
fetch('/awesomeEndpoint', { // fetch is global
method: 'GET',
})
.then((response) => {
if (!response.ok) {
return new Error(response);
}
return response.json();
})
.then((data) => {
dispatch(fetchDataResponse(data));
})
.catch((error) => {
dispatch(fetchDataResponse(error));
});
};
const myRecord = new Record({
loading: true,
aNumber: 0,
aList: List(),
});
const initialState = myRecord({
loading: true,
aNumber: 0,
aList: List()
});
const actions = {
[FETCH_DATA_REQUEST]: state => state.set('loading', true),
[FETCH_DATA_RESPONSE]: (state, action) => {
if(action.payload instanceof Error) {
// do something useful here
return state.set('loading', false);
}
return state
.set('loading', false)
// let's pretend the payload is an array of strings
.set('aList', state.aList.merge(action.payload)),
};
export { fetchData };export default handleActions(actions, initialState);
// file SimpleComponentContainer.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchData } from './myReducerAndActions.js';class SimpleComponentContainer extends Component {
componentDidMount() {
this.props.dispatch(fetchData);
}
render() {
const { loading, aNumber, aList } = this.props.reducer;
return (
<div>
{loading
? <div>show spinning loader</div>
: <div>
<div>show loaded content</div>
<div>{aNumber}</div>
<div>
{aList.map(item => <div>item</div>)}
</div>
</div>}
</div>
);
}
}
// Map all the reducer properties into this component
export default connect(state => ({
reducer: state.myReducer,
}))(SimpleComponentContainer);
- mainStore.js
- myReducerAndActions.js
- SimpleComponentContainer.jsx
const collection = Map({
nestedObject: {
nestedKey: 0,
},
nestedArray: [],
});
console.log(collection.get('nestedObject').nestedKey); // 0
// If collection were immutable all the way to the lowest level
// the code would be
// console.log(collection.getIn(['nestedObject', 'nestedKey']));
const immutableCollection = fromJS({
nestedObject: {
nestedKey: 0,
},
nestedArray: [],
});

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store