React + Redux: when, why and how to use it?
At Ensolvers, we have been working with React Native at different projects for more than a year, combining the simplicity of React with the state management that Redux provides. In this article we describe a bit of our experience with these technologies.
An intro to Redux
Let’s say you have decided to go with React for your next web project -or React Native for a cross platform mobile application like we did and then you eventually stumble with references or posts about Redux and start wondering what’s all the fuzz: It’s a library or a framework? It’s coupled to React in some way? It persist UI data in some way? Do I need it? etc. I wrote this little article to help you get an initial grasp on the core of Redux and hopefully answer at least some of those questions.
So what is Redux?
It can be used with many frontend frameworks (like Angular) or libraries (like React or React Native), it is certainly not a part of React neither it is coupled to it in any way. Its functionality can be extended in many ways (look out for middlewares or selectors).
OK… But why?
For small applications, vanilla React and the setState function, props and callbacks passing combination are OK. But as time passes it is not rare at all that we see frontend applications becoming more and more complex, and this -more often than not- involves managing more state: from caching responses to data created in the UI that is still not persisted along with button states, selected tabs, etc.
In our case we decided to go with Redux to be able to share state between components in a predictable way. At some point we realized it could also be used to decouple components, async services -thanks to something called redux-thunk– and state management, and so we went that way.
The official Redux documentation mentions three core principles. Briefly those are:
- Single source of truth: whatever it’s in the store, it’s the true application state
- State is read only: components can only change state if they declare such intention by what’s called “dispatching an action”. Otherwise, the store state they can request access is expected to be treated as read-only
- Changes are made with pure functions: officially named “reducers”, they can be a simple JS function that receives the current state and an action, and returns the current state or a new state but never alters any of it’s arguments neither modifies possible global objects
So the basic flow goes like this: a component dispatches an action, the action is reduced according it’s type, and may result in a new state. If that’s the case this new state is sent to the store and then the store notifies all components that have declared interest in such change, possibly causing them to re-render.
A note on global and local state
Though for some of you may seem obvious, maybe it is worth mentioning that not necessarily all state is application/global state. In our case we used Redux for managing state that we considered should really be shared across components and routes, but local state it is by no means forbidden. Also, that discerning what is application state and what should be local/component state may not be as easy as it seems. Certain planning is required and maybe even you’ll find yourself tracing a few steps back until you reach a model that suits your needs, like it happened to us during the first sprints.
Setting Redux up
So, in order to setup Redux we started writing a few new JS modules: one for actions, one for holding the type of such actions -enum like, just for convenience-, one for the reducers and one that wires all that together along with some boilerplate initialization code and creates a new store.
Here’s a few sample actions:
Notice that the removeBook action it’s actually async since it calls the service delBook. It also dispatches many actions along the way. That’s why the implementation differs from the action at the top, which is a simple synchronic action: this last one in turn will be reduced.
Speaking about the devil, here we have a sample of the corresponding reducer:
We see this reducer receives the current state -defaulting to an empty array- and an action. According to the action’s type we’ ve seen above, it will return a new array without the removed book. Note that we followed the third principle: we didn’t mutate the state but instead we returned a new one.
Lastly, we wrapped the application main component in a Provider component. This one comes with react-redux and it’s there to keep the store in memory so their child components can access it
Connecting the pieces
To access store state and/or actions a component needs to be connected to the Redux store. This is normally done by simply calling a higher order function conveniently called connect that also comes in the react-redux package. Such function will map the desired application state and/or actions to a given component props.
Since reducers and actions are mostly simple functions it wasn’t hard to test a complete flow with jest which, for those that haven’t heard of it, it’s a pretty standard tool to run tests on JS projects.
In this test we create a new store with the initial state, we dispatch an async action and when that’s resolved all the asserts begin: from matching the store to a JSON schema to validating that our books collection size has been increased in one.
So how it helped us in our project?
To sum it up in our project Redux allowed us to reduce the amount of props and callbacks passing between components, still maintain a service layer but decoupled from those components: components do not directly request data, they dispatch actions and map the store props they need. And lastly write a few tests that could run both locally and from a pipeline.
If you want to take a look at a working and really simple sample React+Redux project here’s the public repository: https://github.com/francisco-pinchentti/React-basics