Redux under the hood
Tags: javascript, tutorial, react, redux
Published: 2021-12-08 14:12:37

**Redux** is a state management library used in a lot of projects.
An new library named `redux-toolkit` has be developed to reduce boilerplate of `redux`. Give it a try it simplifies a lot the code you make, and with typescript <3
To be easily integrated with **React**, **Angular**, ... some bindings libraries exist **react-redux**, **ng-redux**, ...

But that is not the subject of this article. I will not explain the best practices on how to use Redux. If you want more explanation on how to use it, you can see the 
documentation which is awesome: https://redux.js.org/

In this article we are going to see how to implement a **redux** library like. Don't be afraid, it's not so complicated.

_How is the article built?_
We are going to pass on each features of redux, a quick view of what is it need for and then the implementation. Features are:
- store
- reducers
- listeners
- observables
- replaceReducers
- middlewares

Let's get in :)

----

## Store creation

### Some context

To create a store, you have to use the method `createStore` and give it the reducer(s) as first parameter:

```javascript
import { createStore } from "redux";
import userReducer from "./userReducer";

const store = createStore(userReducer);
```

With this store created, you can get two methods:
- `getState` to get the current state
- `dispatch` to dispatch actions wich will be passed to reducers

```javascript 
store.dispatch({
  type: "SET_USERNAME",
  payload: "Bob the Sponge",
});

const state = store.getState();

// Will print 'Bob the Sponge'
console.log(state.userName);
```

#### Reducers

A reducer is a pure function, it's the only one which can change the state (sometimes called also store). The first parameter of this method is the 
current state and the second one the action to handle:

The action is a simple object which is often represented with:
- type: the type of the action to process
- payload: the data useful to process the action


```javascript
const initialState = { userName: undefined };

export default function userReducer(
  state = initialState,
  action
) {
  switch (action.type) {
    case "SET_USERNAME": {
      // The state must stay immutable
      return { ...state, userName: action.payload };
    }
    default:
      return state;
  }
}
```

----


Well, Romain, you told us that you will explain what is under the hood and finally you explain how to use it.

Sorry guys, I needed to put some context before going deep into **Redux** ;)


### Under the hood

> **Note:** The implementation I will show you is based on the version **4.1.2**.

`createStore` is a closure which has a `state` object and returns the methods `getState` and `dispatch`:

```javascript
function createStore(reducer) {
  let state;

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);

    return action;
  };

  // Populates the state with the initial values of reducers
  dispatch({ type: "@@redux/INIT" });

  return { getState, dispatch };
}
```

> **Note:** As you can see, the data is stored in a simple object and it's executed synchronously.
> **createStore** can receive a **preloadedState** to initialize the state. It's not useful if you have initial states on your reducers.

----

## Multiple reducers

For the moment, we saw a simple case with a single reducer. But in applications, you usually more than one. Otherwise `redux` is maybe a little bit overkill for your use case.

Redux can structure the store in a clean way, by dividing our store.

Let's go use the function `combineReducers`.

For example, with the previous reducer `userReducer`, and the new one `settingsReducer`:

```javascript
const initialState = { maxSessionDuration: undefined };

export default function settingsReducer(
  state = initialState,
  action
) {
  switch (action.type) {
    case "SET_": {
      return {
        ...state,
        maxSessionDuration: action.payload,
      };
    }
    default:
      return state;
  }
}
```

The combination of reducers will be:

```javascript
import { combineReducers } from "redux";
import userReducer from "./userReducer";
import settingsReducer from "./settingsReducer";

export default combineReducers({
  user: userReducer,
  settings: settingsReducer,
});
```

We will get the `state`:

```javascript
{
  user: {
    userName: undefined,
  },
  settings: {
    maxSessionDuration: undefined,
  },
}
```

I will tell you amazing, the code of `createStore` doesn't change. So how does `combineReducers` work? 

```javascript
function combineReducers(reducersByNames) {
  return (state, action) => {
    let hasChanged = false;
    const nextState = {};

    Object.entries(reducersByNames).forEach(
      ([reducerName, reducer]) => {
        // A reducer cannot access states of other ones
        const previousReducerState = state[reducerName];

        // Calculate the next state for this reducer
        const nextReducerState = reducer(
          previousReducerState,
          action
        );

        nextState[reducerName] = nextReducerState;

        // Notice the strict equality
        hasChanged =
          hasChanged ||
          nextReducerState !== previousReducerState;
      }
    );

    // If there is no changes, we return the previous state
    // (we keep the reference of the state 
    // for performance's reasons)
    return hasChanged ? nextState : state;
  };
}
```

> **Note:** We can see bellow that if there is 
mutations of the state, then Redux will be completely lost and will not see changes with the strict equality.

![Redux state needs to be immutable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6ixkyuo36ididszqo9z.png)
 

> **Note:** In the real code, there are a lot of checks to be sure everything is working correctly. I have simplified the code to explain how it works without superfluous code.

> **Tip:** The action is passed to all reducers, so the reducer has to to check if it knows how to handle the action. So, we can have multiple reducers which are able to handle a same action.

## Listeners

### What is it?

A listener is a callback we can `subscribe` to potential changes of the Redux state. This listener is directly executed after an event is dispatched. 
Previously I talked about potential changes because, after an action has been dispatched, there is not necessarily changes. For example if none of the reducers know how to handle the event.

Once subscribed, we get a callback to be able to `unsubscribe` it.

## An example of use case

For example if you don't want, or can't use the plugin `Redux DevTools`. It can be useful to be able to see the Redux state at any time. In this case, you can use a listener:

```javascript
import { createStore } from "redux";
import userReducer from "./userReducer";

const store = createStore(userReducer);

store.subscribe(
  () => (window.reduxState = store.getState())
);
```

And now you can see, at any time, the state by typing in your favorite browser's console: `reduxState`.  


### Let's see some code

Our `createStore` becomes:

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

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);

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

    return action;
  };

  const subscribe = (listener) => {
    listeners = [...listeners, listener];

    // Returns the `unsubscribe` method
    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  dispatch({ type: "@@redux/INIT" });

  // We now expose the `subscribe` method
  return { getState, dispatch, subscribe };
}
```

> **Note:** I have simplified a lot the method **subscribe**. In reality, there are a lot of check, especially:
- not to be able to subscribe/unsubscribe when an action is dispatched
- ensure this is a function passed as listener
- be able to call the unsubscribe mutliple times without errors
- ...


## Observable

### Some background

It can be an unknown feature for you, but the store is an `Observable`, so if you use for example **`RxJS`**, you can add an `Observer` to be notified of state's changes.

```javascript
import { from } from "rxjs";
import { createStore } from "redux";
import userReducer from "./userReducer";

const store = createStore(userReducer);

const myObserver = {
  next: (newState) =>
    console.log("The new redux state is: ", newState),
};

from(store).subscribe(myObserver);

// Let's change the username
store.dispatch({
  type: "SET_USERNAME",
  payload: "Bob l'éponge",
});
```

### How does it work?

To be an `Observable`, the store just has to add the `Symbol.observable` (or `@@observable` if `Symbol.observable` is undefined) to its key and implements an `observable` method. 
Its implementation is really simple because it reuses the implementation of `listeners`:

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

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);

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

    return action;
  };

  const subscribe = (listener) => {
    listeners = [...listeners, listener];

    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  const observable = () => ({
    subscribe: (observer) => {
      // The method `observeState` only notifies the Observer
      // of the current value of the state
      function observeState() {
        observer.next(getState());
      }

      // As soon as the Observer subscribes we send the
      // current value of the state
      observeState();

      // We register the `observeState` function as a listener
      // to be notified of next changes of the state
      const unsubscribe = listenerSubscribe(observeState);

      return {
        unsubscribe,
      };
    },
  });

  dispatch({ type: "@@redux/INIT" });

  return {
    getState,
    dispatch,
    subscribe,
    [Symbol.observable]: observable,
  };
}
```


## replaceReducer

### Implementation

When you use code splitting, it can happened you do not have all reducers while creating the store. To be able to register new reducers after store 
creation, redux give us access to a method named `replaceReducer` which enables the replacement of reducers by new ones:

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

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);

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

    return action;
  };

  const subscribe = (listener) => {
    listeners = [...listeners, listener];

    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  const observable = () => {
    const listenerSubscribe = subscribe;

    return {
      subscribe: (observer) => {
        function observeState() {
          observer.next(getState());
        }

        observeState();

        const unsubscribe = listenerSubscribe(observeState);
        return {
          unsubscribe,
        };
      },
    };
  };

  const replaceReducer = (newReducer) => {
    reducer = newReducer;

    // Like the action `@@redux/INIT`,
    // this one populates the state with 
    // initial values of new reducers
    dispatch({ type: "@@redux/REPLACE" });
  };

  dispatch({ type: "@@redux/INIT" });

  return {
    getState,
    dispatch,
    subscribe,
    [Symbol.observable]: observable,
    replaceReducer,
  };
}
```


> **Warning:** I didn't tell you previously, but like `@@redux/INIT`, `@@redux/REPLACE` should be handled in your reducers. Otherwise you will have problems 
with hot reload and your reducers will become unpredictable.


> **Note:** Actually these actions do not have these types, they are suffixed.
![React action types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lssk9h2wtmjvblel2ndu.png)


### Example of usage

Let's use this new method `replaceReducer` to register a new reducer. At the store creation we only register the reducer `userReducer`, then we register the reducer `counterReducer`:

```javascript
export default function counterReducer(
  state = { value: 0 },
  action
) {
  switch (action.type) {
    case "INCREMENT": {
      return { ...state, value: state.value + 1 };
    }
    default:
      return state;
  }
}
```

The replacement of reducers will be:

```javascript
import { createStore, combineReducers } from "redux";
import userReducer from "userReducer";
import counterReducer from "counterReducer";

const store = createStore(
  combineReducers({ user: userReducer })
);

// Will print { user: { userName: undefined } }
console.log(store.getState());

store.replaceReducer(
  combineReducers({
    user: userReducer,
    counter: counterReducer,
  })
);

// Will print 
// { user: { userName: undefined }, counter: { value: 0 } }
console.log(store.getState());
```

> **Note:** If in this example, I have subscribed a listener after the store creation, this one would have been triggered after the reducers modification.

## Middleware

### Presentation

A **middleware** is a tool that we can put between two applications. In the Redux case, the middleware will be placed between the dispatch call and the 
reducer. I talk about a middleware (singular form), but in reality you can put as much middleware as you want.

An example of middleware could be to log dispatched actions and then the new state.


## How do we write a middleware?

I'm gonna directly give you the form of a middleware without explanation because I will never do better than the [official documentation](https://redux.js.org/understanding/history-and-design/middleware#understanding-middleware).

```javascript
const myMiddleware = (store) => (next) => (action) => {
  // With the store you can get the state with `getState`
  // or the original `dispatch`
  // `next`represents the next dispatch
  return next(action);
};
```

Example: middleware of the loggerMiddleware

```javascript
const loggerMiddleware = (store) => (next) => (action) => {
  console.log(`I'm gonna dispatch the action: ${action}`);
  const value = next(action);
  console.log(`New state: ${value}`);
  return value;
};
```

### redux-thunk middleware example

Until now, we dispatched actions synchronously. But in an application it can happened we would like to dispatch actions asynchronously. For example, after having resolved an AJAX call with axios (fetch or another library).

The implementation is really simple, if the action dispatched is a function, it will execute it with `getState` and `dispatch` as parameters. And if it's not a function, it passes the action to the next `middleware` or `reducer` (if there is no more middleware).

```javascript
const reduxThunkMiddleware =
  ({ getState, dispatch }) =>
  (next) =>
  (action) => {
    if (typeof action === "function") {
      return action(dispatch, getState);
    }

    return next(action);
  };
```

The thunk action creator will be:

```javascript
function thunkActionCreator() {
  return ({ dispatch }) => {
    return axios.get("/my-rest-api").then(({ data }) => {
      dispatch({
        type: "SET_REST_DATA",
        payload: data,
      });
    });
  };
}
```

### Store configuration

Before talking about how to configure middlewares with redux, let's talk about **Enhancer**. An enhancer (in redux) is in charge of 'overriding' the original behavior of redux. For example if we want to modify how works the dispatch (with middlewares for instance), enrich the state with 
extra data, add some methods in the store...

The enhancer is in charge of the creation of the store with the help of the `createStore` function, then to override the store created. Its signature is:

```javascript
// We find the signature of the `createStore` method:
// function(reducer, preloadedState) {}
const customEnhancer =
  (createStore) => (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState);

    return store;
  };
```

As you may notice, to use middlewares we need an `enhancer` which is provided by redux (the only one enhancer provided by redux) which is named `applyMiddleware`:

```javascript
// Transform first(second(third))(myInitialValue)
// with compose(first, second, third)(myInitialValue)
function compose(...functions) {
  return functions.reduce(
    (f1, f2) =>
      (...args) =>
        f1(f2(...args))
  );
}

const applyMiddleware =
  (...middlewares) =>
  (createStore) =>
  (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState);

    const restrictedStore = {
      state: store.getState(),
      dispatch: () =>
        console.error(
          "Should not call dispatch while constructing middleware"
        ),
    };
    const chain = middlewares.map((middleware) =>
      middleware(restrictedStore)
    );
    // We rebuild the dispatch with our middlewares
    // and the original dispatch
    const dispatch = compose(chain)(store.dispatch);

    return {
      ...store,
      dispatch,
    };
  };
```

> **Note:** Perhaps you used to use the method `reduce` with an accumulator initialized with a second parameter:  

```javascript
const myArray = [];
myArray.reduce((acc, currentValue) => {
  // Do some process
}, initialValue);
```

If you do not give an initial value (no second parameter), the first value of your array will be taken as the initial value.


> **Note:** Looking at the **applyMiddleware** implementation, you can notice that the middleware's signature could be `(store, next) => action`. 
You can see these PRs which are about this: [PR 784](https://github.com/reduxjs/redux/pull/784), [PR 1744](https://github.com/reduxjs/redux/issues/1744)


The `createStore` becomes:

```javascript
function createStore(reducer, preloadedState, enhancer) {
  // We can pass the enhancer as 2nd parameter
  // instead of preloadedState
  if (
    typeof preloadedState === "function" &&
    enhancer === undefined
  ) {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  // If we have an enhancer, let's use it to create the store
  if (typeof enhancer === "function") {
    return enhancer(createStore)(reducer, preloadedState);
  }

  let state = preloadedState;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);

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

    return action;
  };

  const subscribe = (listener) => {
    listeners = [...listeners, listener];

    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  const observable = () => {
    const listenerSubscribe = subscribe;

    return {
      subscribe: (observer) => {
        function observeState() {
          observer.next(getState());
        }

        observeState();

        const unsubscribe = listenerSubscribe(observeState);
        return {
          unsubscribe,
        };
      },
    };
  };

  const replaceReducer = (newReducer) => {
    reducer = newReducer;

    dispatch({ type: "@@redux/REPLACE" });
  };

  dispatch({ type: "@@redux/INIT" });

  return {
    getState,
    dispatch,
    subscribe,
    [Symbol.observable]: observable,
    replaceReducer,
  };
}
```

An now we can use our middlewares:

```javascript
import loggerMiddleware from "./loggerMiddleware";
import { createStore, applyMiddleware } from "redux";
import userReducer from "./userReducer";

// In this case the enhancer is passed as 2nd parameter
const store = createStore(
  userReducer,
  applyMiddleware(loggerMiddleware)
);
```

## Conclusion

As you can see the code of **Redux** is pretty simple but so much powerful. Data is only stored in an object, and changes are done through reducers.
You can also subscribe to changes, and that's what is done in binding libraries like **react-redux**.
Keep in mind that **Redux** has been developped to be synchronous, and if you to handle asynchronous action creator you will have to use a middleware, like **redux-thunk** or **redux-saga**.
Due to performance, like for **React** state, you can't mutate the state, but recreate a new one. If it's too much boilerplate for you, you can give a chance to **redux-toolkit** which is using **immer** under the hood, to write simpler code and "mutate" the state.
Watch out, do not use **Redux** by default, but only if you need it. 
If you work with **React**, you have some other possibilities like:
- **React state**
- **React context**, probably combined with `useState` or `useReducer` (you can see my article on the performance problem you can encounter [here](https://dev.to/romaintrotard/react-context-performance-5832))
- **atom state management** library like `jotai`, `recoil`.
- **async state manager libraries**: `react-query`, `swr`, ...


---- 

Do not hesitate to comment and if you want to see more, you can follow me on [Twitter](https://twitter.com/romain_trotard ) or go to my [Website](https://www.romaintrotard.com).