As your React application grows in size and complexity, so does the state that you need to manage in your components. Sometimes, this state can become so complicated that it can be difficult to manage with just the useState
hook alone. This is where the useReducer
hook comes in.
What is useReducer?
The useReducer
hook is a built-in React hook that provides a way to manage a more complex state in your components. It works by allowing you to define a state object and an action object that can update that state.
Basic Syntax
The basic syntax for useReducer
is as follows:
import { useReducer } from 'react';
function reducer(state, action) {
// logics to update states
//...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, object);
// ...
}
Let's break this down:
state
is the current state of your applicationdispatch
is a function that you can call to update the statereducer
is a function that takes the current state and an action and returns a new state based on that actionobject/initialstate
is the initial state of your application
Behind the Scene
How does useReducer work?
In the workflow outlined above, the state is initialized with an object. When we want to update the state, we use dispatch
, which takes an object with different properties. In the example above, the object passed to dispatch
includes type
and id
properties. The action
object, which is the second argument of the reducer function, receives this object. The reducer function then returns the updated state based on the type
property and any additional conditions. By using dispatch
to update the state and the reducer function to determine how the state is updated, we can manage complex state more easily and with more predictable results.
So What's the Benefit?
While there may not be many benefits to using useReducer
if you only have a few states to manage, it can be extremely helpful if you are dealing with more than five states. By bringing all states inside a single object, you can avoid the need for separate initialization and updating functions for each state. This also means that all the logic for updating the states is located in one place and is therefore easier to manage. Additionally, updating the state is made easier with the use of a single dispatch
function, which simplifies the overall process.
Here's a complete example useReducer
:
import { useReducer } from 'react';
const ACTION = {
INCREMENT: 'increment',
DECREMENT: 'decrement',
NEW_USER_INPUT: 'newUserInput',
TG_COLOR: 'tgColor'
}
const reducer = (state, action) => {
switch (action.type) {
// Different Logics for different state at one place
case ACTION.INCREMENT:
return { ...state, count: state.count + 1 };
case ACTION.DECREMENT:
return { ...state, count: state.count - 1 };
case ACTION.NEW_USER_INPUT:
return { ...state, userInput: action.payload };
case ACTION.TG_COLOR:
return { ...state, color: !state.color };
default:
throw new Error();
}
}
// All States in a sigle object
const Object={ count: 0, userInput: '', color: false }
function App() {
const [state, dispatch] = useReducer(reducer, object )
return (
<main className="App" style={{ color: state.color ? '#FFF' : '#FFF952' }}>
<input
type="text"
value={state.userInput}
onChange={(e) => dispatch({ type: ACTION.NEW_USER_INPUT, payload: e.target.value })}
/>
<br /><br />
<p>{state.count}</p>
<section> // Just pass the type to action
<button onClick={(() => dispatch({ type: ACTION.DECREMENT }))}>- </button>
<button onClick={(() => dispatch({ type: ACTION.INCREMENT }))}>+</button>
<button onClick={(() => dispatch({ type: ACTION.TG_COLOR }))}>Color</button>
</section>
<br /><br />
<p>{state.userInput}</p>
</main>
);
}
export default App;
Example Explained:
(Skip this if you understand the above code)
Firstly, an ACTION
object is created, which is used to define the different actions that can be taken on the state. This is to avoid typing mistakes, usually, developers do this.
Next, a reducer
function is defined, which takes in the current state
and an action
as parameters. The switch
statement inside the reducer function handles the different cases for the various actions defined in the ACTION
object. Each case returns a new state object with the appropriate updates applied.
The Object
constant is used to define the initial state object, which contains the count, user input, and color states.
Finally, the App
component uses useReducer
to initialize the state object and dispatch actions to update the state. The state is passed as state
and the dispatch function is passed as dispatch
to the component.
In the component, the state properties are accessed using state.count
, state.userInput
, and state.color
. The dispatch
function is used to update the state based on user input and button clicks.
Why use useReducer?
There are several advantages to using useReducer
over useState
:
Simplifies complex state management: As mentioned earlier,
useReducer
comes in handy when there are more states in a single function and so many states have to pass down to the components. By defining areducer
function, you can simplify the logic for updating complex state and reduce the amount of boilerplate code that you need to write.Predictable state updates: With
useReducer
, you can guarantee that state updates are predictable and follow a specific pattern. Because thereducer
function is the only place where state updates can occur, it is easier to reason about the state of your application at any given time.Performance optimization: When using
useState
, updating state triggers a re-render of the component. WithuseReducer
, you have more control over when state updates trigger a re-render. This can be particularly useful in optimizing performance in larger applications.
Recommendation from React:
React recommends using useState
initially, and when it is no longer sufficient for managing your state, useReducer
can be used as an alternative. If your state management needs become even more complex, you may want to consider using other libraries such as Redux or React Query.
Conclusion
The useReducer
hook is a powerful tool for managing complex state in your React components. By defining a reducer
function, you can simplify the logic for updating complex state, guarantee predictable state updates, and optimize performance in larger applications. While it may take some time to get used to the syntax and pattern of using useReducer
, it can be a valuable addition to your React toolkit.
Thank you for taking the time to read this blog. I hope it has been informative and has helped you gain a deeper understanding of the topic. If you have any feedback or suggestions for future content, please let me know!