Click here to Skip to main content
13,898,182 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

18.7K views
100 downloads
8 bookmarked
Posted 30 May 2016
Licenced CPOL

How Redux works

Rate this:
Please Sign up or sign in to vote.
A deeper look at how Redux and React Redux work

Introduction

Redux is a recent Javascript library (started in May 2015) which is described as “predictable state container for JavaScript apps”. It takes the idea from Flux architecture and simplifies it, reduces a lot of boiler plate code required by Flux architecture itself. Despite its small code size, it quickly becomes one of the most popular library used together with ReactJS (at the time of writing this article, its github repository has more than 18000 stars and 800 watch).

For beginners to Redux/Flux architecture, it seems quite intimidating at first since it introduces quite a number of new terminologies and requires us to approach application design in a different way than other Javascript frameworks like AngularJS. If you use Redux with ReactJS, you will need to use another library, React Redux, to glue these two libraries together, which makes the learning process even more challenging. Although there are already a lot of articles online describing Redux/ReactJS and how to use them together, I still find it helpful to understand how Redux/React Redux work internally. In this article, I’ll guide you through the source code of Redux/React Redux and see how the libraries are implemented. Fortunately, both libraries are relatively small in size and we can easily cover their concepts in the scope of this article.

Background

In this article I’ll go through Redux and some parts of React Redux source code. Some prior usage of Redux/ReactJS will be helpful. For a quick refresh, you may want to reference the following link from official Redux documentation: http://redux.js.org/docs/basics/UsageWithReact.html

Also, for some parts of this article, it will be clearer if you have source code of Redux/React Redux open side by side. You can find source code of these 2 libraries here:

Redux: https://github.com/reactjs/redux

React Redux: https://github.com/reactjs/react-redux

Using the code

Here is an overview diagram of main components used in Redux and React Redux

The diagram contains 2 parts: the top part describes Redux components, the bottom part describes React Redux components. I’ll explain these 2 parts in 2 sections below.

Redux Components

Below are some concepts used in Redux:

State store: Redux application maintains only one single state store. Think of this as a client side database. In fact, the recommended practice is to design the state to be as normalized as possible, avoid nesting. This state store is already defined and is handled by Redux library.

Middleware: If you have worked with OWIN before, the middleware concept here is quite similar to middleware in OWIN. The actions dispatched by state store will be processed/transformed by these middleware before passing the control to reducers. You can do anything in middleware, even stopping the action from going to reducers. You can create your own middleware or just use 3rd party middleware. Redux doesn’t provide any middleware out of the box.

Reducers: reducers are pure function, which take previous state, an action as parameters and return new state. There’s no magic in reducers, they are just normal Javascript functions that follow some Redux guideline (. e.g. no mutating of state in the function). For complicated application, reducers can be composed into a tree, where each individual reducer is responsible for one part of the state object.

Actions: these are just plain Javascript objects which follow a convention: include a type property so that reducers know which action it is.

Action creators: simply are action factories.

The state store is created in Redux by calling createStore() in createStore.js. This function returns an object which exposes some API: subscribe(), dispatch(), getState(), etc

Rather than explaining each function in createStore() one by one, I feel it’s clearer to show the flow when one action is dispatched to the store

In the diagram above, SomeComponent is any client of Redux. It can be a React component, can be an Angular service. To Redux, it doesn’t matter how the action object is created. Actually Redux also doesn’t care how action object looks like, it just simply passes it through middleware and reducers (which supplied by client application). Listener 1 and listener 2 are also any client code (.e.g. a React component) that want to listen to changes of Redux store state. These listeners are registered by calling subscribe() in Redux store.

Redux also provides some helper functions, which can be found in the following files:

applyMiddleware.js: this is a store enhancer function. Store enhancer is similar to decorator pattern, it wraps around original store, adds some additional functionality on top of original store. In this case applyMiddleware store enhancer will compose a list of middleware and add them in between store and reducers. This is the only store enhancer provided out of the box by Redux and in most the cases, you will not need any other store enhancer. This function is called during application starts up and its result is passed as 3rd parameter for createStore() function, .e.g:

const enhancer = applyMiddleware(
    thunkMiddleware
);


const appStore = createStore(
  appReducer,
  appState,
  enhancer);

Here it uses some features from ES6 like rest parameter (…middlewares), which is similar to params keyword in .NET, or lambda functions. So when applyMiddleware() is called, it will return a curried function. This curried function will be invoked by createStore() to enhance store functionality.

This applyMiddleware() will first invoke the original createStore() function to create the store:

var store = createStore(reducer, initialState, enhancer);

Then, it creates a chain of middleware, when the chain is invoked, it will go through each middleware one by one, passing in getState() and dispatch(). Finally it modifies original store dispatch function to invoke middlewares first:

chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);

bindActionCreators.js: this file contains 2 functions bindActionCreator and bindActionCreators. These functions are to be used in mapDispatchToProps() function, help us instead of writing like this:

function mapDispatchToProps(dispatch) {
    return {
        someActionCreator: someParameters  => dispatch(someActionCreator(someParameters))
    };
}

we can write like this:

function mapDispatchToProps(dispatch) {
    return bindActionCreators(someActionCreators, dispatch);
}

combineReducers.js: this function allows to combine multiple reducers into a tree structure of reducers, where each reducer is responsible for one part of state object, .e.g. given following state design:

{
    key1: {
        //...
    },

    key2: {
        //...
    }
}

The application can have a single reducer responsible for the whole state object, or can have 2 reducers, one handles key1 state, the other handles key2 state like this:

const appReducer = combineReducers({
    key1: key1Reducer,
    key2: key2Reducers
});

React Redux Components

Connect To Redux without React Redux

As we can see above: by itself, Redux is a very general state management library. It can be used with any other client side Javascript framework like AngularJS. But it has gained popularity especially in React community. To make Redux work with ReactJS, your ReactJS application can manually subscribe to Redux store, and Redux will let you know when the state changes and your application can query for changes using getState() API.

Let’s take a look at counter example from Redux repository (https://github.com/reactjs/redux/tree/master/examples/counter) to see how manual subscription to state store is done. Note that this example uses Webpack to build and transpile javascript. This is a very simple application with minimal UI:

There is a label that displays current count and several buttons that increase the count in different ways.

From the source of index.js, we can see that on page load, the application creates the redux store, and immediately subscribe for state change from the store, provide the callback as render() function:

store.subscribe(render)

when there is any update to the state store, it will invoke the render() callback function, which re-render the Counter React component. Notice that the application uses getState() API to get current state from the store.

function render() {
    ReactDOM.render(<Counter value = {
            store.getState()
        }
        onIncrement = {
            () => store.dispatch({
                type: 'INCREMENT'
            })
        }
        onDecrement = {
            () => store.dispatch({
                type: 'DECREMENT'
            })
        }
        />,
        rootEl
    )
}

Inside counter.js, whenever a button is clicked, the application will invoke onIncrement function that is passed in from index.js, which in turn dispatch an action to redux store, .e.g.:

<button onClick={onIncrement}>+

</button>

So we can totally use Redux without React Redux library. However, this work is quite tedious to setup. Just imagine you need to repeat subscribe(), getState(), dispatch() for all the React components that you want to interact with Redux store. Here is where React Redux library comes into the picture.

Connect to Redux using React Redux

Just for clarity, React components that are wrapped by React Redux and know how to communicate with Redux are normally called smart components or containers. Other React components are called dump components.

Now let’s modify the counter example to use React Redux library instead

First we need to install React Redux using npm:

npm install react-redux –save

We will convert Counter component into a Redux container. Open counter.js, add import to React Redux at the beginning of the file:

import { connect } from 'react-redux'

At the end of the file, instead of export Counter, replace with following:

function mapStateToProps(state) {

    return {

        value: state

    };

}

function mapDispatchToProps(dispatch) {

    return {

        increment: () => dispatch({
            type: 'INCREMENT'
        }),

        decrement: () => dispatch({
            type: 'DECREMENT'
        })

    }

}

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

In render(), incrementIfOdd(), incrementAsync(), change all instances of onIncrement to increment, onDecrement to decrement. For the complete working code, please download the attached zip file.

Majority of the code is still the same like original example. The difference are in 2 additional function mapStateToProps() and mapDispatchToProps(), and instead of exporting Counter component, we export result of calling to React Redux connect()() function. Also, Counter component no longer takes in props value, onIncrement, onDecrement from parent component like original example. Value is now taken from Redux store using mapStateToProps(), onIncrement and onDecrement are changed to increment and decrement created from mapDispatchToProps()

Open index.js, remove the line that subscribes to Redux state store, and change the ReactDOM call to:

ReactDOM.render(

    <Counter store={store}/>,

    rootEl  
)

The Counter declaration is simplified.

Run the example again and everything still works like expected.

Even in this small example, we can see some of the benefits that React Redux library bring:

  • Centralize Redux related code to one area: mapStateToProps(), mapDispatchToProps() and connect()(). Counter component is ignorant of how the value is derived (from Redux store or passed down from parent component)
  • The component doesn’t need to concern with lower Redux API like subscribe(), getState(), just concentrate on mapping from state to its own props
How React Redux is implemented

We will look at several diagrams on how React Redux wraps React component and connects it to Redux.

As we see above, the main function that we use from React Redux is connect(). Let’s consider following call to connect():

connect(mapStateToProps, mapDispatchToProps)(WrappedSmartComponent);

where WrappedSmartComponent is a React component to be wrapped by React Redux. In the counter example, WrappedSmartComponent is Counter component.

When connect()() is called, the following things happen:

So result of this connect()() call is a Connect component that wraps around WrappedSmartComponent. The actual component that is rendered and interacts with Redux store is this Connect component. When this Connect component is rendered by React engine, the following things happen:

When there is some user interaction with Connect component that triggered a state change action, the following things happen:

I already put relevant function calls in the sequence diagrams so you can take a look at those functions in the source code of React Redux if you are interested. The source code is quite straightforward to read if you already know where to look.

I hope now you have a better idea of how React Redux can connect a React component to Redux store and how state change/action dispatch flows work.

History

07 June 2016: Add examples to demonstrate connecting to Redux with and without React Redux

30 May 2016: Initial Version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David Nguyen Hung Phuong
Software Developer
Singapore Singapore
No Biography provided

You may also be interested in...

Comments and Discussions

 
PraiseGood Article, beautiful Pin
lenvo2227-Nov-18 17:09
memberlenvo2227-Nov-18 17:09 
PraiseGood Article, beautiful sequence chart especially Pin
ChampionRoy15-May-17 14:41
memberChampionRoy15-May-17 14:41 
GeneralRe: Good Article, beautiful sequence chart especially Pin
David Nguyen Hung Phuong16-May-17 20:31
professionalDavid Nguyen Hung Phuong16-May-17 20:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05 | 2.8.190306.1 | Last Updated 7 Jun 2016
Article Copyright 2016 by David Nguyen Hung Phuong
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid