Introduction to Redux Actions and Reducers

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to create actions that we can dispatch to our store with Redux.

Create a Store with a Single Reducer

We can create a store with a single reducer by creating a reducer function. Then we can use Redux’s createStore function to create the store from the reducer.

With the store, we get to subscribe to it to get the latest values, and also dispatch actions with it to update the state in the store by passing it to the reducer.

For example, we can write the following code to create the store and run some actions:

import { createStore } from "redux";

function todosReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.todo];
    case "REMOVE_TODO":
      return state.filter(todo => todo !== action.todo);
    default:
      return state;
  }
}

let store = createStore(todosReducer);

store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "ADD_TODO", todo: "eat" });
store.dispatch({ type: "ADD_TODO", todo: "drink" });
store.dispatch({ type: "REMOVE_TODO", todo: "eat" });
Enter fullscreen mode Exit fullscreen mode

In the code above, we created the todosReducer to receive the dispatched actions and then manipulate the state as we wish to do.

We have the switch statement to check the action’s type and then act accordingly.

The action type is indicated in the object that we passed into the dispatch method.

If the action type is “ADD_TODO” , then we insert the todo item in object we have in dispatch ‘s argument to the end of the array, and we spread all the old values before it.

If the action type is “REMOVE_TODO” , then we return a new array that excludes the todo item with the given todo text.

Otherwise, we return what we already have.

Then we use the createStore method to create the store so we can call dispatch to pass in plain objects to manipulate the data in the store.

We can also subscribe to it and then call getState() to get the latest values.

Note that we always make a copy of state and then return something new with it. This prevents accidental changes from mutating objects unintentionally.

Create a Store with a Multiple Reducers

In most apps, we want to store more than one kind of data in our store. We can do that by creating multiple reducers and then use the combineReducer function from Redux to combine them into one store.

To do this we can write something like the following code:

import { createStore, combineReducers } from "redux";

function todosReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.todo];
    case "REMOVE_TODO":
      return state.filter(todo => todo !== action.todo);
    default:
      return state;
  }
}

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

let reducers = combineReducers({
  todos: todosReducer,
  count: countReducer
});
let store = createStore(reducers);

store.subscribe(() => console.log("todos", store.getState().todos));
store.subscribe(() => console.log("count", store.getState().count));

store.dispatch({ type: "ADD_TODO", todo: "eat" });
store.dispatch({ type: "ADD_TODO", todo: "drink" });
store.dispatch({ type: "REMOVE_TODO", todo: "eat" });

store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
Enter fullscreen mode Exit fullscreen mode

In the code above, we have 2 reducers — todoReducer and countReducer .

We combined them into one by calling Redux’s combineReducers function with the name of the state as the property name and the reducer function as the value.

combineReducers combines all the reducers into one reducer, so we can pass it into createStore . We can choose any state name we want.

The store is created by passing the big reducer returned from combineReducers .

Then in the subscribe callbacks, we can use store.getState() to get all the states. Then we can get the todos with the todos property and the count state with the count property.

In the console.log s, we should get something like:

todos ["eat"]
count 0
todos ["eat", "drink"]
count 0
todos ["drink"]
count 0
todos ["drink"]
count 1
todos ["drink"]
count 0
Enter fullscreen mode Exit fullscreen mode

This is because we subscribed to the store and then get the state from each reducer individually.

The dispatch method is called as we did with the single reducer example.

We still pass in the action type and payload if there is one.

Since dispatch works the same as no matter how many reducers we have in the store, we should make sure that no 2 actions have the same name.

Photo by Roberto Nickson on Unsplash

Notes

combineReducers will throw errors in some cases to reduce the chance of committing bugs.

It’ll throw an error if a reducer function doesn’t return the state given to it as the first argument if the action isn’t recognized.

Also, it must never return undefined . It’s too easy to do this via an early return statement. Therefore, combineReducers will throw an error if we do that instead of letting the error manifest somewhere else.

If the state given to it is undefined , it must return the initial state for the specific reducer. This means that the initial state can’t be undefined .

We can specify the initial state as the default argument for the state parameter.

Redux will check for these rules when we’re writing our code.

Conclusion

We can create a store from one more reducer function. The only difference is that we have to use combineReducers to combine multiple reducers into one big reducer in order to pass it into createStore . We can pass in a single reducer straight into createStore .

Then we can use getState to get the latest returned state of all reducers. The states are available from getState() and we can get the properties from the returned object of getState to get those states.

Dispatching actions is the same regardless of how many reducers we have in our store.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .