This tutorial will cover building the core Redux library from scratch. After completing it, you will understand the primary concepts of Redux by applying the library to a simple Redux application.

Developers that are new to Redux are often intimidated by library. However, the core principles are actually very reasonable to understand. Once you pull away additional details like the bindings for React and integrating it into a project, you will learn why it is referred to a simple and predictable state management library.

Core Concepts

“Redux is a predictable state container for JavaScript apps.”

Before diving into the code, we’ll give a brief overview of Redux. If this doesn’t make sense yet, continue on and refer to it again after you complete the example.

Redux is used to store application state. The application state consists of two key inputs:

Data sent from the server User interaction with the UI / application

Redux manages the application state in the store . The state itself is just a plain JavaScript object. The store also provides methods to update the state and read from the state.

At the core of Redux is a publish/subscribe (PubSub) pattern which is a form of an observer pattern similar to the event driven architecture heavily used in JavaScript. In Redux, when a user interacts with the UI, it can dispatch (publish) an action. The concept of an action should not be overthought — it is simply a plain JavaScript object that contains a type which is a unique key to identify it and an additional payload of data.

Using the action, the state is updated based on the type and payload received. Components can be subscribed to state changes and will update the UI based on the the new state tree.

A simple representation of this flow is user interaction publishes an action -> the reducer updates the state -> subscribed components update the UI based on the new state . Built upon this concept are the three core principles of Redux:

Single source of truth. The entire state of the UI is derived from a single object. State is read-only. Neither views nor callbacks can write to state. State can only be changed when emitting an action (the publish) which is just a plain JavaScript object passed as an argument to the reducer. Changes are made with pure functions. The state is not updated directly. The reducer function take the previous state (also a plain object) and creates a new state object based on previous state and the action object. You should always return a new object, never mutate the current one.

That’s it — that’s Redux in a nutshell. If it seems confusing, don’t worry it will all become clear when we implement the code.

Primary Redux Methods

Redux is centered around the store . The store is a JavaScript object which contains the state as well as methods to update ( dispatch() ) and read from ( subscribe()/getState() ) the state. There are also listeners that execute functions based on state changes for the components that are subscribed. Represented visually, the store takes the form:

const store = {

state: {}, // state is an object

listeners: [], // listeners are an array of functions

dispatch: () => {}, // dispatch is a function

subscribe: () => {}, // subscribe is a function

getState: () => {}, // getState is a function

}

To utilize this store object to manage state, we will build a createStore() function. I’ll paste the final form of createStore() below, and then we will break it down one section at a time.

Pretty crazy that the core of Redux is only 18 lines of code, right?

The createStore function takes two arguments, a reducer and an initialState . We will cover the reducer in-depth in a following section, but for now just know that it is a function that indicates how the state should be updated.

The createStore function begins by creating the store object. Then it initializes the store.state = initialState , which will just be undefined if one is not supplied by the developer. The state.listeners are initialized to an empty array.

The first function we define as part of the store is getState() . This simply returns the state when called. store.getState = () => store.state;

We allow our UI to subscribe to changes in the state. The act of subscribing means that we pass a function to the subscribe method, and this listener function is added to the listeners array. typeof listener === 'function' // true .

On every state change, we iterate through the entire array of functions and execute each one.

store.listeners.forEach(listener => listener());

Next we define the dispatch function. The dispatch function is what will be called by a component when a user interacts with the UI. Dispatch takes a single argument which is an action object. The action should fully describe the interaction which was received by the user. Along with the current state, the action is passed to the reducer function which then returns a brand new application state.

After the new state has been created by the reducer, the listeners array is looped through and each function is executed.Typically the getState function is called inside the listener function since the purpose is to react state changes.

Notice that the flow is a very linear and synchronous process. The listener functions are added to a single listeners array. When a user interacts with the application, it can cause an action to be dispatched. This action will create a predictable and discrete change to the state. Then the listener array is looped through in order with every listener function being called.

This process is the unidirectional dataflow. There is only one path to create and react to changes in the application. There are no fancy tricks happening, just a step-by-by path that follows the same straightforward pattern for any interaction with the application.

What is a Reducer function

In the previous section we introduced the reducer function as the input that actually dictates the changes to the state. Let’s take a closer look at what this actually means.

A reducer is a function that takes the state and action and returns the new state. We know at a basic level, it must have the form:

const reducer = (prevState, action) => {

let nextState = {}; // an object representing the new state // ...

// Code that creates new state using the previous state and action

// ... return nextState;

};

Where prevState , nextState , and action are all JavaScript objects.

Let’s take a closer look at the action object to understand how it can be used to update the state. We know that the action will contain a type which is a unique string to indicate the interaction that was triggered by the user.

For example, imagine that you create a simple to-do list using Redux. When the user clicks the submit button to add an item to the to-do list, it could trigger an action with the type ADD_TODO . This is both a human readable way to understand what is happening as well as clear indication to Redux as to the purpose of the action. When adding an item, it will also contain the text of the to-do which can be passed in via the payload. Thus, adding a to-do to the list can be fully realized by the following action object.

const todoAction = {

type: 'ADD_TODO',

text: 'Get milk from the store',

};

Now we can fully build out a reducer for a simple to-do app using this information.

Notice that we are creating a new object every time reducer is called. We utilize the data from the previous state, but create a brand new state object entirely. This is another important principle that makes redux so predictable. By breaking the state into discrete steps, a developer is able to pinpoint exactly what is happening in the application. Although it is beyond the scope of this tutorial, we can optimize updates to the application by only re-rendering sections of the UI that corresponded directly to state changes.

You will commonly see switch statements used with Redux. This is a convenient way to match strings, in our case the type of the action, to the block of code that updates the state. It is no different than writing it using if...else statements like the following.

if (action.type === 'ADD_TODO') {

const nextState = {

todoList: [...prevState.todoList, action.text],

} return nextState;

} else {

return prevState;

}

Redux has no knowledge of anything actually contained in a reducer. It is a function defined by the developer that creates a new state. In fact, the user controls almost everything — the reducer, the actions being utilized, the listener methods being executed via subscribe. Redux is simply a thin layer that ties these together and provides a common interface to interact with the state.

Note: If you’ve seen the combineReducers function before, it is just utility method that allows you to create isolated keys in the state object. It serves as a way to encapsulate the different parts of our state tree that are related and allows the developer to write clean code. Including anymore detail in this tutorial may only serve confuse, so you should just know that it doesn’t actually change the implementation of the single state tree. It separates the tree into chunks and then combines them into the final single state object that we’ve become accustomed to.

Building a simple application using our Redux implementation

We have now covered the entirety of the redux philosophy and core package. We can tie it all together and see it working end-to-end through a simple counter application.

We will make an HTML document with a <div> containing the count from our Redux store. We will open a script tag and target the id="count” node.

<!DOCTYPE html>

<html>

<head><meta charset="utf-8"><title></title></head>

<body>

<div>

Random Count: <span id="count"></span>

</div>

</body>

<script>

const counterNode = document.getElementById('count');

</script>

</html>

Inside the <script> below the counter, we can paste in our createStore function. Below this function, we will create our reducer. This reducer will look for an action with a type of 'COUNT' and then add the count from the action’s payload to the count already stored in the state.

const getInitialState = () => {

return {

count: 0,

};

}; const reducer = (state = getInitialState(), action) => {

switch (action.type) {

case 'COUNT':

const nextState = {

count: state.count + action.payload.count,

}; return nextState;

default:

return state;

}

};

Now that we have a reducer, we can create the store. Using our newly created store, we will subscribe it to updates in the store’s state. On every state change, we will read the count from the state and write it onto the DOM.

const store = createStore(reducer); store.subscribe(() => {

const state = store.getState();

const count = state.count;

counterNode.innerHTML = count;

});

Now that our application is watching for changes to the state, let’s create a simple event listener that will increment the count. The listener will dispatch an action that also sends a random number 1–10 as the count to be added in the reducer.

document.addEventListener('click', () => {

store.dispatch({

type: 'COUNT',

payload: {

count: Math.ceil(Math.random() * 10),

},

});

});

Finally we dispatch an empty action to initialize the state. Since there is no action type, it will hit the default block of the reducer and generate a state object corresponding to what we return from getInitialState() .

store.dispatch({}); // Sets the inital state

Putting it all together, we have the following application.

Your screen should look similar to the following, with the count increasing by a random amount every time you click the screen. Note there are some additional log statements to visualize what is happening as the state changes.

Wrap Up

Not as bad as you thought right? You could even use this implementation of Redux in a normal application without the need for third-party dependencies. I wouldn’t suggest rolling it out in production because there are still edge cases and optimizations, but I hope this simplified Redux and helped with your understanding. The next step is understanding some of the advanced features of Redux.

If all the points of this tutorial didn’t come together on your first read, don’t worry about it. Going through the fundamentals of a pub/sub may be necessary before taking this step. Just continue to practice and refer to this article, and eventually it will all click.

Another great resource is Dan Abramov’s videos. For a deeper understanding of when and why you would want to manage state with Redux, Dan has an article comparing it to the internal state management in React.