Editor’s note: This React Redux tutorial was last updated on 11 September 2023 to update code blocks.
In the ever-evolving world of web development, new JavaScript libraries are always being released. But chasing after each new release without fully understanding their benefits isn’t a good idea.
Redux is an example of a JavaScript library whose enduring popularity is a testament to its value. In this guide, we’ll provide a foundational understanding of Redux, highlighting its functionalities and why you should use it. We’ll explore its benefits by using a simple but practical component.
Jump ahead:
- An introduction to Redux
- When to use Redux
- What is Redux used for?
- Using Redux with React
- State management with Redux
- How Redux works
- Redux middleware
- Benefits of using Redux
An introduction to Redux
Redux is a predictable state container designed to help you write JavaScript apps that behave consistently across client, server, and native environments, and are easy to test.
While it’s mostly used as a state management tool with React, you can use Redux with any other JavaScript framework or library. It’s lightweight at 2KB (including dependencies), so you don’t have to worry about it making your application’s asset size bigger.
With Redux, the state of your application is kept in a store, and each component can access any state that it needs from this store. If you’re just getting started with Redux, this video is a great resource for beginners.
When to use Redux
Not long after its release, Redux became one of the hottest topics of debate in the frontend world.
Redux allows you to manage your app’s state in a single place and keep changes in your app more predictable and traceable, making it easier to understand the changes happening in your app. But all of these benefits come with a set of challenges. Some developers argue that Redux introduces unnecessary boilerplate, potentially complicating what are otherwise simple tasks. However, this depends on the architectural decisions of the project.
So, when should you use Redux? One simple answer to this question is that you will organically realize for yourself when you need Redux. If you’re unsure about whether you need it, you probably don’t. This usually happens when your app grows to a scale where managing app state becomes a hassle and you start looking for ways to make it simplify it.
What is Redux used for?
Redux is used to maintain and update data across your applications for multiple components to share, all while remaining independent of the components.
In a large application, there is a need to store the state at a central location and share it among the different components. That is where the Redux store comes into the picture:
If the data needs to be passed from a parent to a child deep down the tree, this can still be accomplished using React utilities like Context. But when it comes to sharing the state between components on the same level, Redux is the inevitable option.
Using Redux with React
As we mentioned earlier, Redux is a standalone library that can be used with different JavaScript frameworks including Angular, Inferno, Vue, Preact, React, etc. However, Redux is most frequently used with React.
This is because React only allows for a uni-directional flow of data. That means data cannot be sent from a child to a parent; it has to flow downward from the parent to the child. This thought model works very well with Redux where we cannot directly modify the state. Instead, we dispatch actions that intend to change the state, and then separately, we observe the resulting state changes.
State management with Redux
State management is essentially a way to facilitate the communication and sharing of data across components. It creates a tangible data structure to represent the state of your app that you can read from and write to. That way, you can see otherwise invisible states while you’re working with them.
Most libraries, such as React and Angular, are built with a way for components to internally manage their state without the need for an external library or tool. This works well for applications with few components, but as an application grows larger, managing states shared across components becomes a hassle.
In an app where data is shared among components, it might be confusing to actually know where a state should live. Ideally, the data in a component should live in just one component, so sharing data among sibling components becomes difficult.
For example, to share data among siblings in React, a state has to live in the parent component. A method for updating this state is provided by the parent component and passed as props to these sibling components.
Here’s a simple example of a login component in React. The input of the login component affects what is displayed by its sibling component, the status component:
import React, { useState } from 'react'; const App = () => { const [count, setCount] = useState(0); const handleIncrement = (incrementBy) => { const numberToIncrement = incrementBy || 1; setCount(count + numberToIncrement) } return ( <div> <BigCountDisplay count={count} /> <CounterButton onIcrement={handleIncrement} /> </div> ); } export default App;
Remember, this data is not needed by the parent component, but because its children need to share data, it has to provide a state.
Now imagine what happens when a state has to be shared between components that are far apart in the component tree. Basically, the state will have to be lifted up to the nearest parent component and upwards until it gets to the closest common ancestor of both components that need the state, and then it is passed down. This makes the state difficult to maintain and less predictable.
It’s clear that state management gets messy as the app gets more complex. This is why you need a state management tool like Redux to more easily maintain these states. Now, let’s take a look at Redux concepts before considering its benefits.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to use the React children prop with TypeScript
- Explore creating a custom mouse cursor with CSS
- Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
How Redux works
The way Redux works is simple. There is a central store that holds the entire state of the application. Each component can access the stored state without having to send down props from one component to another.
There are three core components in Redux — actions, store, and reducers. Let’s briefly discuss what each of them does. This is important because they help you understand the benefits of Redux and how it can be used.
We’ll be implementing a similar example to the login component above but this time in Redux.
💡
store
refers to the object that holds the application data shared between components.
Redux actions
Redux actions are events. They are the only way you can send data from your application to your Redux store. The data can be from user interactions, API calls, or even form submissions.
Actions are plain JavaScript objects that must have:
- A
type
property to indicate the type of action to be carried out - A
payload
object that contains the information that should be used to change the state
Actions are created via an action creator, which is a function that returns an action. Actions are executed using the dispatch()
method, which sends the action to the store:
Here’s an example of an action:
{ type: "INCREMENT", payload: { incrementBy: 5, } }
And here is an example of an action creator. It is just a helper function that returns the action:
const getIncrementAction = (numberToIncrement) => { return { type: "INCREMENT", payload: { incrementBy: numberToIncrement, } } }
Redux reducers
Reducers are pure functions that take the current state of an application, perform an action, and return a new state. The reducer handles how the state (application data) will change in response to an action:
💡 A pure function is a function that will always return the same value if given the same parameters, i.e., the function depends on only the parameters and no external data.
Reducers are based on the reduce
function in JavaScript, where a single value is calculated from multiple values after a callback function has been carried out.
Here is an example of how reducers work in Redux:
const CounterReducer = (state = initialState, action) => { switch (action.type) { // This reducer handles any action with type "LOGIN" case "INCREMENT": return state + action.incrementBy ? action.incrementBy : 1 default: return state; } };
Hence, if the initial state was 12, after the action to increment it by five gets dispatched and processed, we get the new value of the state, i.e., 17.
💡 Reducers take the previous state of the app and return a new state based on the action passed to it. As pure functions, they do not change the data in the object passed to them or perform any side effect in the application. Given the same object, they should always produce the same result.
Redux store
The store is a “container” (really, a JavaScript object) that holds the application state, and the only way the state can change is through actions dispatched to the store. Redux allows individual components to connect to the store.
It is highly recommended to keep only one store in any Redux application. You can access the stored state, update the state, and register or unregister listeners via helper methods. Basically, the components get to update the store via actions and then subscribe to the changes to the store so they know when to re-render:
Redux toolkit
Redux is a great utility for state management in React. But, as we mentioned before, it can introduce a lot of boilerplate into your application due to the verbosity of its API. Because of this, it is recommended to use the Redux Toolkit while using Redux with React. Let’s look at a few benefits that this provides.
Setting up a store with Redux toolkit
While setting up a store with pure Redux can be quite cumbersome, with Redux Toolkit, it is a single call to the configureStore
function. This is how we can set up a store that has a counterReducer
slice in it:
import { configureStore } from '@reduxjs/toolkit' import counterReducer from './counterReducer' const store = configureStore({ reducer: { counter: counterReducer, }, }) export default store
Creating an action using Redux Toolkit
Redux Toolkit also provides us with utilities to generate actions. In Redux, actions are just normal objects with a type and a payload field. The createAction
utility from Redux Toolkit returns us a function. We can call that function with any object and it will get dispatched as the payload for that particular action:
const addTodo = createAction('INCREMENT') addTodo({ val: 5 }) // {type : "INCREMENT", payload : {val : 5}})
Creating the reducer with Redux Toolkit
The reducer in Redux is a normal, pure function that takes care of the various possible values of state using the switch
case syntax. But that means several things need to be taken care of — most importantly, keeping the state immutable.
Redux Toolkit provides the createReducer
utility, which internally uses immer. This allows us to freely mutate the state, and it will internally be converted to an immutable version. This is how a reducer defined with Redux Toolkit would look like:
import { createAction, createReducer } from '@reduxjs/toolkit' const increment = createAction('counter/increment') const incrementByAmount = createAction('counter/incrementByAmount') const initialState = { value: 0 } const counterReducer = createReducer(initialState, (builder) => { builder .addCase(increment, (state, action) => { state.value++ }) .addCase(incrementByAmount, (state, action) => { state.value += action.payload }) })
Notice how easy that makes the task of playing with the state. The difference is more evident when the state is more complex with several nested objects. In the scenario without Redux Toolkit, we have to be careful to keep all the operations immutable but the equivalent code with Toolkit is much more simplified:
// a case without toolkit, notice the .map to create a new state case 'TOGGLE_TODO': { const { index } = action.payload return state.map((todo, i) => { if (i !== index) return todo return { ...todo, completed: !todo.completed, } }) } // a case with toolkit, notice the mutation which is taken care internally .addCase('TOGGLE_TODO', (state, action) => { const todo = state[action.payload.index] // "mutate" the object by overwriting a field todo.completed = !todo.completed })
With that in place, let’s now move to learning about what Redux middleware are and how they can further simplify the overall experience.
Redux middleware
Redux allows developers to intercept all actions dispatched from components before they are passed to the reducer
function. This interception is done via middleware, which are functions that call the next method received in an argument after processing the current action.
Here’s what a simple middleware looks like:
function simpleMiddleware({ getState, dispatch }) { return function(next){ return function(action){ // processing const nextAction = next(action); // read the next state const state = getState(); // return the next action or you can dispatch any other action return nextAction; } } }
This might look overwhelming, but in most cases, you won’t need to create your own middleware because the Redux community has already made many of them available. If you feel middleware is required, you will appreciate its capacity to enable great work with the best abstraction.
Benefits of using Redux
When using Redux with React, states will no longer need to be lifted up. This makes it easier for you to trace which action causes any change.
As you can see in the example above, the component does not need to provide any state or method for its children components to share data among themselves. Everything is handled by Redux, which greatly simplifies the app and makes it easier to maintain.
This is the primary reason why you should use Redux, but it’s not the only benefit. Take a look at the list below for a summary of what you stand to gain by using Redux for state management.
Redux makes the state predictable
In Redux, the state is always predictable. If the same state and action are passed to a reducer, the same result is always produced because reducers are pure functions. The state is also immutable, which makes it possible to implement difficult tasks like infinite undo and redo. It is also possible to implement time travel — that is, the ability to move back and forth among the previous states and view the results in real time.
Redux is maintainable
Redux is strict about how code should be organized, which makes it easier for someone with knowledge of Redux to understand the structure of any Redux application. This generally makes it easier to maintain, and also helps you segregate your business logic from your component tree. For large scale apps, it’s critical to keep your app more predictable and maintainable.
Debugging is easy in Redux
Redux makes it easy to debug an application. By logging actions and state, it is easy to understand coding errors, network errors, and other forms of bugs that might come up during production.
Besides logging, it has great DevTools that allow you to time travel actions, persist actions on page refresh, and more.
For medium- and large-scale apps, debugging takes more time then actually developing features. Redux DevTools makes it easy to take advantage of all Redux has to offer.
Performance benefits
You might assume that keeping the app’s state global would result in some performance degradation. To a large extent, that’s not the case. React Redux implements many performance optimizations internally so that your own connected component only re-renders when it actually needs to.
Ease of testing
It is easy to test Redux apps because they rely on pure functions. This means tht tests can simply call a pure function with specific parameters and check if the return value matches the expected result.
State persistence
With Redux, you can persist some of the app’s state to localStorage and restore it after a refresh.
Server-side rendering
Redux can also be used for server-side rendering. With it, you can handle the initial render of the app by sending the state of an app to the server along with its response to the server request. The required components are then rendered in HTML and sent to the clients.
Conclusion
In this guide, we discussed the major features of Redux and how Redux can be beneficial to your app. While Redux has many helpful features, that does not mean you should add Redux to all of your apps. It’s important to know when and when not to use Redux.
One major benefit of Redux is the ability to navigate through the state’s history, allowing developers to observe how the state has changed throughout the app’s lifecycle. However, it is important to implement Redux only if it fits your requirements and your project needs a state management tool.
Thank you for this clear and well-written article.
If you want to learn redux from analogies, I found this super useful https://link.medium.com/OYJvQWEapwb
great article. my confusion about far children and immediate child resolved.thankyou
What a great article.
Great, well-written article
Great Article For Beginners
@nighodaro, loved the article, thank you. Though, I’m having a hard time understanding why do we take in the last code snippet – usename for Status?
““
Doesn’t a service with rxjs do all that much more simpler?
Excelente article
Well explained. Thank you so much for great blog 🙂
Nice One !
Very well explained article than you so much.. keep up the good work… god bless you
Hi, Thanks for your article. Do you think it could be possible and enough to just use a React Context do avoid the props drilling problem ?
Amazing written, Thank you so very much
Wow. This is better
Excellent. Thanks.
Thank you. Its really help in understanding the Redux concept.
What an amazing and helpful explanation! I’ve read several articles but this one was the most clear one! It helps me understand the concepts of Redux
well written, thank you!
Thank you so much…I can explain redux to a 5 years old effortlessly now
Absolutely great article. Very well concise and clear. Thank you very much!
Great Article ✌🏾✌🏾✌🏾 Thank you very much
Great article. Never imagined I would learn it so much easily. Highly recommended for everyone, who wishes to know about Redux. Thanks a ton.