Build Your Own Redux From Scratch

Redux is a state management library that helps you write applications that behave consistently and are easy to test. It's most commonly used with React for building user interfaces.  It allows you to centralize the state of your app in a single store, and use actions and reducers to update the state in a predictable way. This article will explore how to create your own redux-like library from scratch in plain old JavaScript. 

Note

The Redux-like library we're going to build will not have all the features of Redux and I won't recommend anyone to use this in a production environment. The goal of building this library is to help you understand how Redux works under the hood.

And now that we're on the same page, let's get started!

Core Redux Concepts

The basic idea behind Redux is that you have a centralized object that stores all the information of your app called state which can predictably update the state. To accomplish this, Redux has the following basic structure:

State

A state is a plain JavaScript object that represents the state of the application at a specific point in time. The state is managed by the store and can be accessed by any component in the application via the store. The state is read-only and the only way to change the state is by dispatching an action, which is a plain JavaScript object that represents an intention to change the state.

Store

A store is an object that holds the application's state tree. There is only a single store in a Redux application. A state is a plain JavaScript object that represents the state of the application at a specific point in time.

Action

An action is a plain JavaScript object that represents an intention to change the state. Actions are the only way to get data into the store. You send them to the store using the dispatch method.

Reducer

A reducer is a pure function that takes the previous state and an action as arguments and returns a new state. It describes how an action transforms the state into the next state.

Building our Redux

If you've worked with Redux in the past, you must be aware of the createStore method that creates the store. We'll start by creating our own createStore method which will have three methods, dispatch, subscribe, and getState.

export const createStore = (reducer, initialState) => {
    let state = initialState; // setting our initial state
    const listeners = []; // array of functions that'll hold the subscribe callbacks
    function getState() {
        // will return the current state
    }
    function dispatch(action) {
        // will dispatch an action
    }
    function subscribe(fn) {
        // will hold the listeners
    }
    return {
        getState,
        dispatch,
        subscribe,
    };
};

As you can see we've created a barebones createStore function that accepts two parameters the reducer function and the initialState and returns an object that consists of three methods getState, dispatch, and subscribe. 

We've also created two variables inside this function called state and listeners. The state variable as the name suggests will hold the state of our Redux store and the listeners array will hold the callback functions which we'll pass to subscribe method. As soon as the createStore method is triggered we're assigning the initial state we're passing as the second parameter to the state variable.

Now, let's implement these functions, starting with the most basic one i.e. getState function. This function will simply return the current state hence we can simply say:

function getState() {
    return state;
}

Next, we'll take care of subscribe function.

function subscribe(fn) {
    listeners.push(fn);
}

It simply grabs the callback function passed as a parameter and pushes it inside the listeners array.

And last but not least, is the dispatch function.

function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach((listener) => listener(state));
}

This function simply accepts the action and pass it to the reducer and assigns the output of the reducer back to the state variable. Once we've received the output from the reducer it passes a new state to all the listeners using the forEach method.

After, putting all this together we'll get,

export const createStore = (reducer, initialState) => {
  let state = initialState;

  const listeners = [];
  function getState() {
    return state;
  }
  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach((listener) => listener(state));
  }
  function subscribe(fn) {
    listeners.push(fn);
  }
  return {
    getState,
    dispatch,
    subscribe,
  };
};

And that's it our redux is ready!

Testing our Redux

Now that we've completed building out the custom redux library let's take it out for a spin. We're simply building a program that'll perform increments and decrements on a number. First, we'll create a reducer and initial state object.

export const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        value: state.value + 1,
      };
    case 'DECREMENT':
      return state.value < 1
        ? {
            value: 0,
          }
        : {
            value: state.value - 1,
          };
    case 'RESET':
      return {
        value: 0,
      };
  }
};
export const initialState = { value: 0 };

The initial state will have a value property with a default value of zero. The reducer has three cases. INCREMENT, DECREMENT, and RESET. The increment case will increase the value property by one, decrement case decrease the value property by one unless it's zero. And reset property will reset the value to zero.

Now let's build a react component to utilize our redux library and reducer.

import * as React from 'react';
import { createStore } from './my-redux';
import { initialState, reducer } from './reducer';

export default class App extends React.Component {
  store;
  state = { value: 0 };
  constructor(props) {
    super(props);
    this.store = createStore(reducer, initialState);
  }
  componentDidMount() {
    this.store.subscribe((state) => {
      this.setValue();
    });
    this.setValue();
  }
  setValue() {
    this.setState({
      value: this.store.getState().value,
    });
  }
  plus() {
    this.store.dispatch({
      type: 'INCREMENT',
    });
  }
  minus() {
    this.store.dispatch({
      type: 'DECREMENT',
    });
  }
  reset() {
    this.store.dispatch({
      type: 'RESET',
    });
  }
  render() {
    return (
      <div>
        {`${this.state.value}`}
        <div>
          <button onClick={this.plus.bind(this)}>+</button>
          <button onClick={this.minus.bind(this)}>-</button>
          <button onClick={this.reset.bind(this)}>Reset</button>
        </div>
      </div>
    );
  }
}

We're using a class-based React component that simply renders the value property from the store to the screen along with three buttons to increment, decrement, and reset the state.

And that's pretty much it! You can access a working demo of this library at stackblitz.

I hope you enjoyed this tutorial. In case you've any queries or feedback, feel free to drop a comment or connect me on Twitter @harshalslimaye.