React + Redux: Higher-order reducers with combineReducers()
Using Redux for global state management in your React application is a complete game changer — no longer must you tactically navigate an ever-expanding (and confusing) web of local state to get the right data to the right components. But with great power comes great responsibility… like adhering to the single responsibility principle (aka, the S in SOLID)! In this article we’ll walk through how to ensure your app is SoC compliant while still leveraging the power of Redux.
The example app I’ll use to demonstrate this is a GIF library app I recently completed for module 5 of Flatiron School’s software engineering course. The app allows a user to create an account/login, search GIPHY’s API, and add/remove GIFs to a collection they’ve created. The user can then copy a link to that GIF to share it with friends, or ‘like’ a collection that caught their eye. Basically, it’s a user-friendly way to organize your favorite GIFs and share them with friends (because we’re always looking for the perfect GIF for any given moment, right?).
Setting the Scene
The first order of business is deciding exactly how to break out your reducers. Essentially, you should ask yourself — which Redux actions manipulate which part(s) of my global state? Let’s take a look at what you should be aiming for, using my User reducer as an example:
const user = (state = { status: 'pending' }, action) => {
switch (action.type) {
case 'setStatus':
return { ...state, status: action.payload }
case 'setUser':
return { ...action.payload, status: 'resolved' }
case 'noUser':
return { status: 'idle' };
case 'logout':
return { status: 'idle' }
default:
return state;
}
}export default user;
You’ll notice that this reducer only affects the portion of Redux state that it has access to, and vice versa. The SoC gods are pleased!
Now, rinse and repeat for the rest of your Redux actions. My app utilizes Collection, gifSearch, and User reducers — notice how these align with individual app features, keeping the app SOLID and intuitively structured.
Reduce the Reducers
So you’ve got your reducers separated (ideally in a ‘reducers’ folder, duh) and are asking yourself, “Okay, so how do we wire these up with the Redux store?”.
First, create a new index.js
file in your reducers folder. Here is where you’ll need to import each individual reducer, plus the combineReducers function from the Redux library, just like this:
import gifSearch from './gifSearch';import collection from './collection';import user from './user';import { combineReducers } from 'redux';
Next, we’re going to define a variable rootReducer
(ensuring it’s exported so that we can access it in our store.js
file) and set it to the value of combineReducers
with each individual reducer passed in:
export const rootReducer = combineReducers({ collection, gifSearch, user })
Putting It All Together
Now it’s time to hook our rootReducer
up to our store — the final stretch. Your store.js
file should look something like this, once all said and done (I’m using the Redux devtools Chrome extension, which is an optional second argument to the compose()
function):
import { createStore, compose, applyMiddleware } from 'redux';import thunk from 'redux-thunk';import { rootReducer } from './reducers/index';export function configureStore() { return createStore( rootReducer, compose( applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) );}export const store = configureStore();
Now, assuming that your store is being passed into your <Provider>
component, you’re all wired up!
One last thing to note before calling it quits though — anywhere you’re accessing state via mapStateToProps, you’ll now need to specify which reducer you’re accessing, like so:
const mapStateToProps = state => {
return {
collections: state.collection.collections,
activeCollection: state.collection.activeCollection,
status: state.user.status,
username: state.user.username
}
}
And with that, your Redux state is no longer a convoluted mess consisting of dozens of lines of code — your app, your colleagues, AND your brain will thank you for it as your app grows and grows. This article only scratches the surface though, and for a deeper understanding of what functions like combineReducers
are doing under the hood, be sure to check out the Redux docs.