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" });
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" });
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
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.