How to Redux with React

10,491 reads

Official redux logo from https://github.com/reactjs/redux/tree/master/logo

Pre requisites: the reader is already knows JavaScript & React. If not, please read this first. It is a refresher into frontend engineering with React.

According to the official documentation, Redux is a predictable state container for JavaScript apps.

So to start with, let us begin with a why. It is mainly three-fold:

Pure function Depth agnostic state subscription Ease of testing

Pure function

Definition: A function that operates solely on the passed in arguments and has no side effects is a Pure Function.

In math, we have sin(x) . In JavaScript, we have,

const sum = (a, b) => a + b;

Because the sum function works solely off the passed in inputs, given arguments are the same, it will produce the same output irrespective of the number of times or under what conditions we invoke it.

As in, sum(4, 5) will always return to 9. Time and again. Without fail.

Like sin(x) our sum(x, y) is predictable.

How does it apply to redux?

Redux is based on the idea that there should be only a single source of truth for your application state, be it UI state like which tab is active or Data state like the user profile details:

{

first_name: 'John',

last_name: 'Doe',

age: 28',

}

All of these data is retained by redux in a closure that redux calls a store . It also provides us a recipe of creating the said store, namely createStore(x) .

The createStore function accepts another function, x as an argument. The passed in function is responsible for returning the state of the application at that point in time, which is then persisted in the store.

This passed in function is known as the reducer .

This is a valid (but not exactly useful) reducer function:

export default function reducer(state={}, action) {

return state;

}

Redux has this concept of actions . An action is a plain old JavaScript object of the following shape:

{

type: 'SOME_NAME',

payload: 'SOME FREE FORM DATA'

}

For example, say we have a micro blogging site and a user posts some content. This is what the resulting action could look like:

{

type: 'Add_USER_POST',

payload: {

content: 'A quick brown fox jumped over the lazy dog',

}

}

Now, this action is passed into the reducer to come up with the updated state as a function of the passed in action .

To handle it, let us update our reducer to this:

export default function reducer(state = {posts: []}, action) {

switch (action.type) {

case 'Add_USER_POST':

return {

...state,

posts: [

...state.posts,

{

content: action.payload.content,

}

]

};

default:

return state;

}

}

First, we added a default property posts to our default state and initialised it with [] .

Next, we simply added a switch-case block that switches on action.type . Because our action had a type of Add_USER_POST it will be intercepted by the 1st case in our reducer’s switch-case and it will return a fresh object composed out of our existing state and add the newly added post to the posts array.

This returned object will then be added to the store.

One important thing to note in here is that the store can only be updated by dispatching an action.

Your App dispatches an action , it is passed into reducer ; the reducer returns a fresh instance of the state; the store notifies your App and it can begin it’s re render as required.

The store exposes 3 functions: dispatch , getState & subscribe .

dispatch as the name suggests, dispatches actions to be consumed by the reducer.

getState returns the snapshot of your application state at this moment.

subscribe accepts a callback function that is fired for every modification / update to the state tree.

These are used by UI library specific bindings to handle the redux to app bridge. For React , the recommended binding is react-redux .

Say we have a Posts component that renders a list of posts. This is how we write it:

import React, { Component } from 'react';

import { connect } from 'react-redux';

class Posts extends Component {

render() {

const posts = this.props.posts.map((post, i) => (

<li key={i}>

{post.content}

</li>

));

return (

<h3>Posts</h3>

<ul>

{posts}

</ul>

)

}

}

const mapState = state => ({

posts: state.posts,

});

const mapDispatch = dispatch => ({});

export default connect(

mapState,

mapDispatch

)(Posts);

As you can see, the component Posts simply receives the list of posts as props and renders it as received.

We define two functions: mapState & mapProps and pass them as callbacks to connect .

The connect does a getState and provides the current snapshot of state to mapState as argument;

it then passes dispatch function as an argument to mapDispatch and finally passes in the Posts component to the function returned by the connect(x, y) call.

Internally, it subscribes to the state changes and passes data all the way to the component to see if a re render is required.

One can argue it is too much work for a simple app and single source of truth can be implemented in pure React with state in some top level component and passing it down as props.

However, as your app grows, you add tens of more components and every single time you need to pass in data from top root all the way to leaf components. Needless to say, it can quickly become cumbersome.

Redux allows us to subscribe to any part of the state tree from any depth.

Depth agnostic state subscription

As we have seen, our Posts component can simply subscribe to the posts property in the store.

The Posts component has absolutely no relation to its parent; as in it can be in an n-level depth and it’s logic or it’s parent/rendering component’s logic will not change.

Say you have a search feature that renders a SearchBar which ultimately renders a set of SearchItems component. Both <SearchBar/> & <SearchItems/> can directly subscribe to the store.

Tomorrow, there can be a requirement to move the search inside the Posts component and other than the CSS changes, component hierarchy wise, all we have to do is cut-paste.

Ease of testing

Because reducers are all pure functions, there is no mocking effort required when we write unit tests for them.

Simply pass in the action and expect a state snapshot in return. Your application logic can now be fully unit tested and as such bulletproof.

Take redux for a spin at stackblitz. Alternatively, play around with the embed here (scroll down till end).

Thanks for reading.

Hit *clap* if you think this was worth your while :)

Find me on linkedin or revert back to me on comments.

Tags