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 a basic store in the app and use it.
Installation
Redux is available as an NPM package. We can install it by running:
npm install --save redux
with NPM or:
yarn add redux
with Yarn.
Basic Example
We can use redux to store the state of an app by creating a store and reducer.
Then we can subscribe to the store to get the data and dispatch actions to make changes to the state.
For example, we can write:
import { createStore } from "redux";
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + action.payload || 1;
case "DECREMENT":
return state - action.payload || 1;
default:
return state;
}
}
let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });
In the code above, we have the counter
reducer function which gets the action type and payload and then applies it accordingly.
The counter
function checks for action type INCREMENT
and DECREMENT
and return the new state with the payload
added to it.
Then we create the store
by running:
let store = createStore(counter);
After that, we can subscribe to the store by calling subscribe
on the store and then call getState
within the callback for it as follows:
store.subscribe(() => console.log(store.getState()));
Then we can dispatch actions to the store by calling dispatch
on it with an object with the type
and payload
, which will be passed into the counter
function in the action
parameter.
Therefore, the state
will be updated by the action
since for the first dispatch
call:
store.dispatch({ type: "INCREMENT", payload: 2 });
The type
will be passed in as action.type
and payload will be set as action.payload
to counter
.
action.type
is ‘INCREMENT’
and action.payload
is 2.
Likewise, action.type
is ‘DECREMENT’
and action.payload
is 1 in the second call.
Note that we don’t mutate the state directly in counter
. We return a new state by deriving it from the existing state.
Three Principles of Redux
Three principles apply to the way Redux is built.
Single Source of Truth
Redux stores state in a single place. This makes getting data easy since we only have to get it in one place.
Debugging is also easy because of that. This makes development faster.
Some functionality that was hard to implement now can be easy since now the state is stored in a single tree.
State is Read-Only
The state is read-only so it can’t be changed by accident. We have to express an intent to change the state.
There aren’t subtle race conditions to watch out for since all changes are centralized and happen one by one in strict order.
Since actions are just plain objects, they can be logged, serialized, stored and replayed for debugging and testing purposes.
Changes are Made With Pure Functions
Reducers are pure functions that take the previous state and action and return the next state. We return new state objects instead of mutation previous state.
We can start with a single reducer and then split it off into smaller reducers that management other parts of the state tree.
We can control how they’re called, pass additional data, or make reusable reducers.
Flux Architecture
Redux concentrates all model update logic in one layer of an app. This is one part of the Flux architecture that’s adopted by Redux.
Redux doesn’t have a dispatcher. It relies on pure functions instead of event emitters to change state.
Pure functions are easy to compose and doesn’t need extra management.
Redux assumes that we never mutate our data. We should always return new objects. This is easy now that we have the spread operator for objects.
Photo by Kelly Sikkema on Unsplash
RxJS
We can turn a Redux store into an Rxjs Observable by subscribing to it and calling nexr
to return the latest state.
For example, we can write:
import { createStore } from "redux";
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + action.payload || 1;
case "DECREMENT":
return state - action.payload || 1;
default:
return state;
}
}
let store = createStore(counter);
function toObservable(store) {
return {
subscribe({ next }) {
const unsubscribe = store.subscribe(() => next(store.getState()));
next(store.getState());
return { unsubscribe };
}
};
}
const counterObservable = toObservable(store);
counterObservable.subscribe({
next(count) {
console.log(count);
}
});
store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });
In the code above, we have:
function toObservable(store) {
return {
subscribe({ next }) {
const unsubscribe = store.subscribe(() => next(store.getState()));
next(store.getState());
return { unsubscribe };
}
};
}
const counterObservable = toObservable(store);
counterObservable.subscribe({
next(count) {
console.log(count);
}
});
store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });
The toObservable
returns an Observable which we can subscribe to since it returns a subscribe
method.
The Redux store’s subscribe
method returns an unsubscribe
function so we can unsubscribe to it when we no longer need it.
Then when we call dispatch
above, we’ll get the new values logged in the next
function above.
Conclusion
We can store an app’s data in a central place with Redux. To do this, we create a store and then subscribe to it to get the latest state from it.
Then we call dispatch
on the store with a plain object with the action type and payload to change the store to the latest state.