Redux is a pattern and library for managing and updating application state, using events called "actions". In other words, the Redux pattern provides state management for JavaScript apps. You can use the Redux library with any frontend framework, such as React, Angular, or even Vue. In the end, you can implement the Redux pattern in any vanilla JS application.
This article covers what Redux is, why you need it, how it works, the benefits, and when not to use Redux. To make Redux clearer, we’ll update you with code examples to make the concept easier to grasp. Let’s get started!
What is Redux?
As mentioned in the introduction, Redux is a pattern that facilitates state management. It allows you to maintain a predictable state container for your JavaScript apps. This is important for consumer-facing applications where the interface changes based on user input.
On top of that, Redux prevents race conditions where two components simultaneously try to update the state. It accomplishes this task by defining actions that get dispatched to reducers.
Each action contains a type (also seen as an identifier) and a payload. Next, a reducer accepts the action and changes the state based on the received action type and payload.
Reducers are pure functions, which means they are predictable. A pure function returns the same output for the same input. You can use reducers to generate a new application state.
Lastly, to notify our interface that the application state has changed, we can subscribe to data changes. Whenever the application state changes, we update the UI.
It’s a simple, yet elegant solution to facilitate predictable state management for small and large applications. Luckily, most popular frameworks offer support for Redux. Here's a quick overview:
React -> react-redux: You can add Redux to your React application by installing the
react-redux
dependency.Angular -> @ngrx/store or @angular-redux/store: Both options work well to implement Redux into your Angular application. According to npmtrends.com,
@angular-redux/store
is the most popular library in terms of weekly downloads.Vue -> vuejs-redux: This dependency offers a light-weight implementation of Redux for Vue with only 45 lines of code and no dependencies. It provides the same API as the
react-redux
dependency.
Next, why should you use Redux?
Why use Redux?
Instead of directly exploring examples, let’s learn more about the problem Redux solves.
As with many applications, they start small. Imagine a pyramid structure of seven components where each component as two child components. Each component manages its state. However, situations occur where we have to share a state with a child component or a child component wants to modify the parent component’s state.
Do you see the problem? While our application grows to a higher number of components, maintaining data consistency becomes a hairy challenge. It’s not an easy task to manage each component’s state while sharing it with many other components. You’ll likely experience data inconsistency bugs, a fearsome nightmare for frontend developers.
Image source: Codecentric blog
As shown in the image, Redux takes away the responsibility from individual components to manage a state. Instead, we create a single store that handles our state management. On top of that, all communication regarding reading, updating, or creating data happens via the store. It prevents data inconsistency bugs from appearing. Moreover, components can listen to state changes to update the UI and avoid these data inconsistency bugs.
Lastly, you can install the Redux DevTools that give you insights into your application’s current state to simplify debugging or testing your application. It’s a great incentive to get started with Redux.
Next, let’s explore Redux with code examples.
Redux with code examples - How does it work?
Let’s recap the Redux cycle before we take a look at our code example. This is how the Redux cycle looks like:
- Users interact with the interface and triggers an action
- Action with/without payload is sent to a reducer using the dispatcher
- Reducer checks if it handles the action and produces a new state based on the action and its payload
- State changes are notified via subscription methods
- UI renders again based on state changes received via the subscription method
Now, let’s explore how this works using code. We’ll create a simple webpage that allows you to increase or decrease a counter in the state using plus and minus buttons. We’ll use a single index.html
document that contains a script tag with all the necessary code.
You can find the completed code via CodeSandbox.io.
Step 1: Exploring index.html
First, let’s create an index.html
document with the following HTML setup. This will render the current counter value and buttons to increase or decrease the counter.
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<p>
Counter: <span id="count">0</span>
<button id="increment">+</button>
<button id="decrement">-</button>
</p>
</div>
<script>
</script>
</body>
</html>
Next, let's take a look at how we can define actions.
Step 2: Define Redux actions
Let’s define actions we want to dispatch to the reducer when the user clicks the increase or decrease button.
We can listen for the click
event and dispatch a new action to the Redux store, which contains the reducer.
Make sure to dispatch an object that contains the type
property. This property contains the name of the action. As a best practice, use the format <reducer-name>/<action>
. This makes it easier to identify actions as multiple components can send the same action. In our example, we will name the reducer counter
. Therefore, we get the following actions:
counter/increment
-
counter/decrement
<script>
document
.getElementById("increment")
.addEventListener("click", function () {
store.dispatch({ type: "counter/increment" });
});
document
.getElementById("decrement")
.addEventListener("click", function () {
store.dispatch({ type: "counter/decrement" });
});
</script>
Next, let’s define the reducer.
Step 3: Define a reducer
As we can dispatch multiple actions to the reducer, we’ll use a switch statement to handle the different actions.
First, we define the initial state for the application by setting the count
equal to zero. Next, we define a counterReducer
function that accepts the current state and the dispatched action.
Two scenarios are possible here:
- Reducer receives an
counter/increment
action to increase the counter - Reducer receives an
counter/decrement
action to decrease the counter
Note that we use the reducer function as an argument for the createStore
function to define a new Redux store for our application.
<script>
// Define an initial state for the app
const initialState = {
count: 0
};
// Create a "reducer" function that determines what the new state
// should be when something happens in the app
function counterReducer(state = initialState, action) {
switch (action.type) {
case "counter/increment":
return { ...state, count: state.count + 1 };
case "counter/decrement":
return { ...state, count: state.count - 1 };
default:
// If the reducer doesn't care about this action type,
// return the existing state unchanged
return state;
}
}
</script>
Tip: Don’t forget to add a default
clause to your switch
statement that returns the current state. You may have multiple reducers for your application. When you dispatch an action, the action is sent to all reducers and not only the intended one. Therefore, you want all reducers to return the current state to avoid errors and only the intended reducer to return the updated state.
Step 4: Subscribing to state changes
In this step, we want to subscribe to state changes to update the UI when the state has changed.
We define a render
method that retrieves the current state and renders the count
property. Further, we pass this render
function as an argument to the store.subscribe
method to update the UI automatically when the state changes. This method exposed by our newly created store will call the render
function when the state has changed.
<script>
// Our "user interface" is some text in a single HTML element
const countEl = document.getElementById("count");
// Whenever the store state changes, update the UI by
// reading the latest store state and showing new data
function render() {
const state = store.getState();
countEl.innerHTML = state.count.toString();
}
// Update the UI with the initial data
render();
// And subscribe to redraw whenever the data changes in the future
store.subscribe(render);
</script>
Make sure to take a look at the completed code. You can use the CodeSandbox to play with the code yourself or fork the example.
Completed code below:
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<p>
Counter: <span id="count">0</span>
<button id="increment">+</button>
<button id="decrement">-</button>
</p>
</div>
<script>
const initialState = {
count: 0
};
function counterReducer(state = initialState, action) {
switch (action.type) {
case "counter/increment":
return { ...state, count: state.count + 1 };
case "counter/decrement":
return { ...state, count: state.count - 1 };
default:
return state;
}
}
const store = Redux.createStore(counterReducer);
const countEl = document.getElementById("count");
function render() {
const state = store.getState();
countEl.innerHTML = state.count.toString();
}
render();
store.subscribe(render);
document
.getElementById("increment")
.addEventListener("click", function () {
store.dispatch({ type: "counter/increment" });
});
document
.getElementById("decrement")
.addEventListener("click", function () {
store.dispatch({ type: "counter/decrement" });
});
</script>
</body>
</html>
That's it!
What are the benefits of using Redux?
There are many benefits to using Redux. The most prominent benefit is improved state management for your application. Yet, there are many other benefits.
Easy debugging and testing. You can use the Redux DevTools or log the state to understand better what’s happening in your application.
Reducer functions can be tested quickly. As reducer functions are pure functions, they produce the same output for the same input. Therefore, testing pure functions become a simple task.
Hook monitoring tools. You can hook monitoring tools to your application’s state to monitor the state in real-time. This improves visibility for your application and allows you to track different metrics.
Predictable outcome. Every action produces a predictable outcome. Your state store acts as a single source of truth. Therefore, you can avoid data inconsistency bugs and don’t have to worry about data synchronization issues between components.
When not to choose Redux?
For beginners, it’s an obvious choice to opt for Redux. Yet, you don’t always need Redux to manage the state of your application.
Applications that consist of mostly simple UI changes most often don’t require a complicated pattern like Redux. Sometimes, old-fashioned state sharing between different components works as well and improves the maintainability of your code.
Also, you can avoid using Redux if your data comes from a single data source per view. In other words, if you don’t require data from multiple sources, there’s no need to introduce Redux. Why? You won’t run into data inconsistency problems when accessing a single data source per view.
Therefore, make sure to check if you need Redux before introducing its complexity. Although it’s a reasonably efficient pattern that promotes pure functions, it might be an overhead for simple applications that involve only a couple of UI changes. On top of that, don't forget that Redux is an in-memory state store. In other words, if your application crashes, you lose your entire application state. This means that you have to use a caching solution to create a backup of your application state, which again creates extra overhead.
If you want to learn more about Redux, check out the Redux FAQ section, which contains tons of interesting questions you might have about Redux. Also, check out this amazing analogy for Redux by Hitesh Choudhary.
Top comments (1)
One of the best and most overlooked alternatives to Redux is to use React's own built-in Context API.
Context API provides a different approach to tackling the data flow problem between React’s deeply nested components. Context has been around with React for quite a while, but it has changed significantly since its inception. Up to version 16.3, it was a way to handle the state data outside the React component tree. It was an experimental feature not recommended for most use cases.
Initially, the problem with legacy context was that updates to values that were passed down with context could be “blocked” if a component skipped rendering through the
shouldComponentUpdate
lifecycle method. Since many components relied onshouldComponentUpdate
for performance optimizations, the legacy context was useless for passing down plain data.The new version of Context API is a dependency injection mechanism that allows passing data through the component tree without having to pass props down manually at every level.
The most important thing here is that, unlike Redux, Context API is not a state management system. Instead, it’s a dependency injection mechanism where you manage a state in a React component. We get a state management system when using it with
useContext
anduseReducer
hooks.A great next step to learning more is to read this article by Andy Fernandez: scalablepath.com/react/context-api...