Redux Without React
Tags: redux, javascript, programming, webdev
Published: 2022-06-09 14:11:13



In this blog i will try to explain `redux` library without react. Just Redux. 

Why? Because if we understand redux then we’ll understand `react-redux` (redux bindings for react) better. 

So Redux is a relatively very small API

- `compose`
- `createStore`
- `bindActionCreators`
- `combineReducers`
- `applyMiddleware`

Yes, just these five functions or methods. We will understand each one by one.

Let’s start with compose.

### compose

Its just a utility that come along with **redux**, which is just a **javaScript function** that takes functions as arguments and returns a new function that executes them from right to left. Yup thats it.

To understand that better, lets say we have a have a bunch of functions that take a string

```jsx
const makeUppercase = (string) => string.toUpperCase();
const logString = (string) => console.log(string) 
const boldString = (string) => string.bold();
```

We could call them all like this:

```jsx
logString(makeUppercase(boldString("redux")));
```

But, let’s say if we wanted to pass this function  as an argument to another function what would we do? 

```jsx
const boldenTheStringAndUppcaseItThenLogIt = (string) => 
logString(makeUppercase(boldString(string)));
```

This could take too long , so we `compose` . Which helps use make a new function that will execute all the function passed as arguments to `compose(args)` 

```jsx
const boldenTheStringAndUppcaseItThenLogIt = redux.compose(
logString , makeUppercase, boldString
)

boldenTheStringAndUppcaseItThenLogIt("")
```

this will execute the functions from `right to left`(boldString → makeUppercase → logString) as “redux” as argument sting, and  we’re done with `compose.` That is what `compose` is.

So far we have covered 20% for the `redux API`

---

Now let’s understand `createStore` . Create store creates a store. A store is where we keep all our state.

But it doesn’t just create a store, however  

```jsx
// reducer is a function that we need to pass to the createStore,
// we will cover what a reducer shortly..

let store = redux.createStore(reducer) 
console.log(store)

// dispatch: ƒ dispatch() {}
// subscribe: ƒ subscribe() {}
// getState: ƒ getState() {}
// replaceReducer: ƒ replaceReducer() {}
```

four more functions 

- `dispatch`
- `subscribe`
- `getState`
- `replaceReducer`

### But, what is a reducer after all ?

A reducer is also just a function that takes in two arguments 

- state (state of the application)
- action (an action is event for example ,  web socket messages, action functions , etc.)

and it returns the new state.

so basically ,a reducer is a function where the `first argument is the current state of application` and `the second is something that happened`. Somewhere inside  the function, we figure out what the new state of the world should to be based on whatever happened.

```jsx
// this is not how you should write a reducer , just for example
const reducer = (state, action) => state //(new state)
```

Let’s try to understand this with an example. Let’s say we have a counter app, and we want to increment the counter, we will have an increment action.

now, actions are also just functions, and they need to have only have type, 

```jsx
const initialState = { value: 0 };

const reducer = (state = initialState, action) => {
  if (action.type === "INCREMENT") {
    return { value: state.value + 1 };
  }

  return state;
};

// which can also be written as

const initialState = { value: 0 };

const INCREMENT = "INCREMENT"; // 

const incrementCounterAction = { type: INCREMENT }; // only requires type , 
 // others are optional
 // payload : {}, meta: {}, error.
const reducer = (state = initialState, action) => {
  if (action.type === INCREMENT) {
    return { value: state.value + 1 };
  }

  return state;
};
```

Alright, there are a few things that we need to understand here.

- You'll notice that we're creating a new object rather than mutating the existing one.
- This is helpful because it allows us to figure out what the new state is  depending on the current state
- *We also want to make sure we return the existing state if  an action we don't care about comes through the reducer.*

You will also notice we made a constant called `INCREMENT`.  The main reason  we do this  is because we need to make sure that we don’t accidentally mis-spell the action type—either when we create the action or in the reducer. 

Let’s say we have a counter which has an increment button and add input , which increment the counter and add to the counter whatever value we put in the input. 

```jsx
const initialState = { value: 0 }; // state of the application

const INCREMENT = "INCREMENT"; // constants 
const ADD = "ADD"; // constants

// action creators (fancy name for functions)
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });

const reducer = (state = initialState, action) => {
  if (action.type === INCREMENT) {
    return { value: state.value + 1 }; // new state
  }

  if (action.type === ADD) {
    return { value: state.value + action.payload }; // new state
  } 

  return state; // default state 
};
```

So far we have understood compose and what createStore does and the four functions it creates, what a reducer and action is. But we need to still understand the four functions createStore creates.

 So lets start with 

- `getState` (this function returns the current value of the state in the store)
- `dispatch` (how do we get actions into that reducer to modify the state? Well, we dispatch them.)

```jsx
const store = createStore(reducer);

console.log(store.getState()); // { value: 0 }
store.dispatch(increment());
console.log(store.getState()); // { value: 1 }
```

- `susbscribe` - This method takes a function that is invoked whenever the state in the store is updated.

 

```jsx
const subscriber = () => console.log('this is a subscriber!' store.getState().value);

const unsubscribe = store.subscribe(subscriber);

store.dispatch(increment()); // "Subscriber! 1"
store.dispatch(add(4)); // "Subscriber! 5"

unsubscribe();
```

 - `replaceReducer` used for code splitting

---

### bindActionCreators

Earlier in the blog we read about action (which are functions). So guess what bindActionCreators does? ... binds actions functions together.!!

In the example below we have actions and reducers 

```jsx
const initialState = { value: 0 }; // state of the application

const INCREMENT = "INCREMENT"; // constants 
const ADD = "ADD"; // constants

// action creators (fancy name for functions)
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });

const reducer = (state = initialState, action) => {
  if (action.type === INCREMENT) {
    return { value: state.value + 1 }; // new state
  }

  if (action.type === ADD) {
    return { value: state.value + action.payload }; // new state
  } 

  return state; // default state 
};

-----------------------------------------------------------------------------

const store = createStore(reducer);

/// notice we have to do this like this everytime 
store.dispatch(increment());

```

Notice we have to dispatch the actions `store.dispatch(increment())` every time, this could be tedious in a large application. 

we could do this in a cleaner way like this 

```jsx
const dispatchIncrement = () => store.dispatch(increment());
const dispatchAdd = (number) => store.dispatch(add(number));

dispatchIncrement();
dispatchAdd();
```

 or we could use `compose` . remember compose for earlier in the blog ?

```jsx
const dispatchIncrement = compose(store.dispatch, increment);
const dispatchAdd = compose(store.dispatch, add);
```

We could also do this using `bindActionCreators` , takes two arguments 

- action creators (action functions)
- dispatch

```jsx
const actions = bindActionCreators(
  {
    increment,
    add,
  },
  store.dispatch
);

actions.increment();
```

Now we don’t have to use bindActionCreators all the time, but it’s there.

With that we have completed 60% of the redux API.

---

### combineReducers

Guess what this does ? Yes, combines reducers. When we have big applications we have many reducers.

For example when we have a blog application. We would have reducers for storing the user information, reducers for storing the blogs, reducers for storing the comments.  

In these big application we split the reducers into different files. `combineReducers` is used when we have multiple reducers and want to combine them .

Say we have an application with users and tasks. We can assign users and assign tasks

```jsx
const initState = {
  users: [
    { id: 1, name: "ved" },
    { id: 2, name: "ananya" },
  ],
  tasks: [
    { title: "some task", assignedTo: 1 },
    { title: "another task", assignedTo: null },
  ],
};
```

reducer file

```jsx

const ADD_USER = "ADD_USER";
const ADD_TASK = "ADD_TASK";

const addTask = title => ({ type: ADD_TASK, payload: { title } });
const addUser = name => ({ type: ADD_USER, payload: { name } });

const reducer = (state = initialState, action) => {
  if (action.type === ADD_USER) {
    return {
      ...state,
      users: [...state.users, action.payload],
    };
  }

  if (action.type === ADD_TASK) {
    return {
      ...state,
      tasks: [...state.tasks, action.payload],
    };
  }
};

const store = createStore(reducer, initialState);

store.dispatch(addTask("Record the song"));
store.dispatch(addUser("moksh")); // moksh is my brother's name 😃 

console.log(store.getState());

```

It would be nice if we could have two different reducers for the users and the tasks 

```jsx
const users = (state = initialState.users, action) => {
  if (action.type === ADD_USER) {
    return [...state, action.payload];
  }

  return state;
};

const tasks = (state = initialState.tasks, action) => {
  if (action.type === ADD_TASK) {
    return [...state, action.payload];
  }

  return state;
};

// now we need to combine the reducers and we do that using combineReducers
const reducer = redux.combineReducers({ users, tasks });

const store = createStore(reducer, initialState);
```

> **Fun Fact!**: All actions flow through all of the reducers. So, if you want to update two pieces of state with the same action, you totally can
> 

---

### applyMiddleware

Theres a lot of stuff that Redux can not do by itself, so we can extend what Redux does  using middleware and enhancers. 

Now what are middleware and enhancers ? They’re just functions that let us add more functionality to redux.

To be more accurate, enhancers are functions that take a copy of `createStore` and a copy of the arguments passed to `createStore`   before passing them to `createStore`. This allows us to make libraries and plugins that will add more capabilities how the store works.

We see enhancers in use when we use the Redux Developer Tools and when we want to dispatch asynchronous actions. 

## **The Actual API for `createStore()`**

`createStore()` takes one, two, or three arguments.

- `reducer` (required)
- `initialState` (Optional)
- `enhancer` (Optional)

Let’s try to understand this with an example, below is an enhancer that monitors the time it takes to update a state.

```jsx
const monitorUpdateTimeReducerEnhancer = (createStore) => (
  reducer,
  initialState,
  enhancer
) => {
  const monitorUpdateTimeReducer = (state, action) => {
    const start = performance.now();
    const newState = reducer(state, action);
    const end = performance.now();
    const diff = round(end - start);

    console.log("Reducer process time is:", diff);

    return newState;
  };

  return createStore(monitorUpdateTimeReducer, initialState, enhancer);
};

const store = createStore(reducer, monitorReducerEnhancer);
```

If its still not clear , let look at another example that logs the old state and new state using enhancers.

```jsx
const reducer = (state = { count: 1 }) => state;

const logStateReducerEnhancer = (createStore) => (
  reducer,
  initialState,
  enhancer
) => {
  const logStateReducer = (state, action) => {
    console.log("old state", state, action);
    const newState = reducer(state, action);
    console.log("new state", newState, action);

    return newState;
  };

  return createStore(logStateReducer, initialState, enhancer);
};

const store = createStore(
  reducer,
  logStateReducerEnhancer)
);

store.dispatch({ type: "old state" });
store.dispatch({ type: "new updated state" });
```

Ok cool, but what is applyMiddleware used for? it’s used for creating enhancers out of chain of middleware. What does that mean , maybe be some code would help understand better.

```jsx
const enhancer = applyMiddleware(
  firstMiddleware,
  secondMiddleware,
  thirdMiddleware
);
```

So Middleware have the following API

```jsx
const someMiddleware = (store) => (next) => (action) => {
  // Do stuff before the action reaches the reducer or the next piece of middleware.
  next(action);
  // Do stuff after the action has worked through the reducer.
};
```

`next` is either the next piece of middleware or it's `store.dispatch`. If you don't call `next`, you will swallow the action and it will never hit the reducer.

Here is an example of the logState middleware that we looked at earlier using this 

```jsx
const logStateMiddleware = (store) => (next) => (action) => {
  console.log("State Before", store.getState(), { action });
  next(action);
  console.log("State After", store.getState(), { action });
};

const store = createStore(reducer, applyMiddleware(logStateMiddleware));
```

Lets also see how we can do this with the monitorUpdateTime 

```jsx
const monitorUpdateTimeMiddleware = (store) => (next) => (action) => {
  const start = performance.now();
  next(action);
  const end = performance.now();
  const diff = Math.round(end - start);

  console.log("Reducer process time is:", diff);
};

const store = createStore(reducer, applyMiddleware(monitorUpdateTimeMiddleware));
```

With this we have covered all of the Redux API. I hope this blog was helpful and you learnt something new.