Loading Inter font-family
Loading Josephine Sans font-family
Loading Hallywood font-family

Learn Redux by Building Redux

Showrin Barua
Showrin Barua / June 04, 2022
11 min read----

Learn Redux by Building Redux by Showrin

Redux, a popular name in the web engineering world, is a simple state management library. It was created by Dan Abramov and Andrew Clark in 2015. It can be used in any web application with any framework. That means we can use Redux with React, Vue, Angular, and even Vanilla JS. If we notice its npm download count, that is huge, more than 7M+ weekly downloads. Just think. How many apps are using this simple library! 🤯 So learning Redux is highly recommended when you want to be a web-app magician.

In this article, we'll learn the fundamental things about Redux. But, I strongly believe, you will understand a library better when you can replicate it. That's why here we'll try to build a Redux-like library (MiniRedux) after learning fundamentals about Redux. Let's kick it off 💥💥💥

Building blocks of Redux

In the following section, I'm going to discuss some of the important building blocks of Redux.

  1. Store

  2. Dispatch

  3. Action

  4. Reducer

  5. Subscriber

  6. Getter


  • Store is the global space where all the states are stored that can be accessed from all the sections of the app. It's a javascript object where each property-value pair is known as a slice.

  • Whenever we need to update a slice of the store, we have to call the dispatch() function with a specific action.

According to functional programming, we can't mutate data. If we have a store like,

store = {
  count: 10,
  user: "Showrin",
}

we can't simply update the value like the following.

store.count = 11;

According to the functional programming paradigm, we have to clone the store first, then update the count, and then use the new store.

const newStore = { ...store, count: 11 }

That's why we can't directly access the states inside the Redux store, can't directly update the Redux store. For reading the states we have to use getState() and for updating the states we have to use dispatch().

  • The dispatch function always expects an object which is called an Action. Usually, we declare an action object with a type and payload keys. It's not a rule from Redux but easy to read and maintain the codebase. Example of an action:

    {
      type: 'removeTask',
      payload: {
        id: 1,
      }
    }
    
  • Reducer is the place where we can decide:

    • Which state/slice should be updated
    • How to calculate the value of a state
    • What value should be assigned to a state

    In reducer, we can keep all our business logic, and according to action type, we update a state/slice.

  • Subscriber is a higher-order function that receives a listener function as an argument and invokes this listener function whenever the store receives an update.

  • Earlier I said that we can't directly access states/slices inside the redux store. As an entry point, redux provides us a getter function getState() for reading the states inside the store.

Flow of Updating Redux Store

Think about a very common scenario in daily store.

A store has many different products. Imagine this store as the redux store and different products as different key-value pairs (or slices) in the redux store.

If the shopkeeper wants to buy some products what should he do first? Yes, he has to decide which products to buy. Let's assume that he has decided to buy 20kg of sugar. Now, imagine this decision as an action. It can be written like:

{
  type: 'buySugar',
  payload: {
    quantity: 20,
    unit: 'kg',
  }
}

After taking this decision, shop keeper will call or inform importer about his decision. Think it as dispatch() that receives an action (ex: buy sugar).

Once importer has decision from shop keeper, importer will start processing that request. Imagine importer as the reducer function.

Importer sends products to the shop keeper and shop-keeper loads the store with those products. Here actually what is happening is the store is updated.

Once the shop keeper updates the store, he will sends the message to all the subscribed-customers who are using products those are just updated. And yes, this is the last phase of the update-flow that is invoking listener functions provided to subscribe().

Store Update Flow of Redux - in Daily Shop (Drawn by Showrin)

Can you relate the above scenario with Redux? I believe you can. To sum up the scenario, I'm explaining the update-flow in text-book language.

  • First of all, we have to define an action with type key.

  • Then we have to invoke the dispatch() with that action to initiate an update.

  • Redux invokes the reducer function with latest state and the dispatched action. This reducer function returns the updated state based on the action-type and redux replace the old state with this new state.

  • After an update redux invokes the listener function that was passed as an argument to subscribe function.

Store Update Flow of Redux - in coding (Drawn by Showrin)

Write a Simple Vanilla JS program using Redux

Whenever you're about to use redux in your app, first thing you have to do is decide the actions.

Here, I did the following tasks.

  1. Define action-types (ex: ADD_TASK)

  2. Define action with action-types and payload

  3. Write the reducer function to return the updated state based on action-type

  4. Create store with that reducer

  5. Get the state from the reducer

  6. Write a listener functions to log the latest state and pass it to subscribe()

  7. Dispatch ADD_TASK action several times

  8. Unsubscribe one of the listener functions

  9. Dispatch ADD_TASK action some more times

If you notice the console, you'll see:

[]
[Object]
[Object, Object]
[Object, Object, Object]
[Object, Object, Object, Object]

As we subscribe a listener that prints the latest state, when the state/slice inside store gets changed, that's why we are getting this console.log().

After 4th dispatch, we unsubscribed a listener. That's why there is no console.log() of that listener happened after unsubscribing.

Write our own Redux

Yeah, I know 🍩 this is the most interesting part 🍩 of this article. So, let's build our own redux 🎉🎉🎉

Replicate createStore function

Redux gives us one vital method createStore and that is the first step of creating a redux store in any application.

Basically, createStore returns an object which is our store. This store object has some methods. If you log the store in the console, you'll see the following methods.

{
  @@observable: ƒ observable(),
  dispatch: ƒ dispatch(action),
  getState: ƒ getState(),
  replaceReducer: ƒ replaceReducer(nextReducer),
  subscribe: ƒ subscribe(listener),
}

We won't write all of the methods. We'll write the following important methods.

  • getState

  • dispatch

  • subscribe

Let's create a file miniRedux.js and then write our first function createStore.

export function createStore (reducer) {
  let state;

  function getState () {}

  function dispatch () {}

  function subscribe () {}

  return {
    getState,
    dispatch,
    subscribe,
  }
}

Explanation

  • We've created a function createStore that takes reducer as an argument.

  • Inside the function, we declared a state variable and that is the global state of the app.

  • We've also created three empty functions getState, dispatch, subscribe. We'll work on them in upcoming sections.

  • Then we returned an object and expose those three functions as its method.

  • Finally, we did named-export the createStore function to keep sync with the redux library.

Replicate getState function

In redux, when we write store.getState(), it returns the current global state.

It's a simple getter function to read the global state.

export function createStore (reducer) {
  let state;

  function getState () {
    return state;
  }

  ...
  ...

  return { ... }
}

Explanation

  • Earlier we created a variable state. In the getState function, we just returned that state.

  • One thing to notice. There is no way to access the state variable except getState, even no way to update state. In the next section, we'll work on updating the state.

Replicate dispatch function

This section is the most important among all three methods of the redux store.

Since there is no way to update the state, we have to provide a function to update the state. This is the dispatch function that redux uses for updating the global state.

Let's have a small recap about the dispatch function. When we call the dispatch function we have to provide an action object as its argument. Then, dispatch calls the reducer function that was passed to createStore function. While calling the reducer, it passes the state and action as arguments of reducer. I think you can remember how we wrote the reducer.

function reducer (state=[], action) { ... }

See? What did we write in the parameter section? Yes, state and action. And those are passed by the dispatch function.

And lastly state gets updated with the value returned by the reducer.

I think this recap is enough for now. Let's write our dispatch function.

export function createStore (reducer) {
  let state;

  ...
  ...

  function dispatch (action) {
    state = reducer(state, action);
  }

  ...
  ...

  return { ... }
}

Explanation

  • We called the reducer function with state and action as arguments.

  • The value (new state) returned by the reducer is assigned to the state. Thus the global state gets updated.

Replicate subscribe function

This section is a little bit complex. The only functionality left is the subscribe function. A listener function is passed to this function that is called after each state update.

The subscribe function returns a function unsubscribe. Once the unsubscribe function is called, the listener won't be called anymore on the state update.

One point to note, we can subscribe with multiple listeners and unsubscribe any of them independently. Let's have a look at the following example.

...
const unsubscribe1 = subscribe(listener1);
const unsubscribe2 = subscribe(listener2);
...
...
dispatch(...);
dispatch(...);

unsubscribe1();

dispatch(...);
dispatch(...);
...

Here we subscribe with two listeners listener1, and listener2. But after two dispatches, we unsubscribed listener1. We called dispatch four times in total. The listener1 will be called only 2 times(for the first two dispatches). But listener2 will be called 4 times as it's not unsubscribed.

Let's write our subscribe function.

export function createStore (reducer) {
  let state;
  let listeners = [];

  ...
  ...

  function dispatch(action) {
    ...

    listeners.forEach((listener) => {
      listener();
    });
  }

  function subscribe(listener) {
    let isSubscribed = true;

    listeners.push(listener);

    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }

      const listenerIndex = listeners.indexOf(listener);

      isSubscribed = false;
      listeners.splice(listenerIndex, 1);
    };
  }

  return { ... }
}

Explanation

  • To keep track of whether a listener is subscribed or not, we declared a variable isSubscribed.

  • Then we added the listener function (from parameter) to our listeners' list.

  • In the unsubscribe function we did two things.

    • First we set false to isSubscribed. If isSubscribed is false, unsubscribe function will stop being executed.

    • And then, we find and removed the respective listener from the listeners.

  • And at the end, we called all the listener functions inside dispatch after updating the state.

    If a listener is unsubscribed, we're removing it from the listeners. As a result, it won't be called anymore inside dispatch.

Now just change the import from import { createStore } from "redux" to import { createStore } from "./miniRedux";. Here's the whole code for MiniRedux.

Conclusion

With this implementation, we've built our own redux 🏆 Cheers 🥂🥂 Yes, it doesn't have all mechanisms of redux. We didn't do any parameter-type checking here. But I believe, now we don't have that much fear about redux that we had earlier 💪💪 Because now we know how things work in redux. To know more details, go through the codebase of redux https://github.com/reduxjs/redux/blob/master/src/createStore.ts.

If you find this article helpful, feel free to share it with your network 🤝🤝🤝

Happy Coding 💻💻💥💥

ReduxState ManagerJavascript

Stay Tuned To Get Latests