useReducer Hook
Overview
In React, there are two main hooks that we can use for state management: useState and useReducer hooks. useReducer Hook allows developers to handle complex state manipulations (handling multiple states that rely on complex logic) & updates. useReducer Hook is an alternative to useState Hook and we use it when the React components need to be optimized or when the next state value is dependent upon the previous state value.
What is useReducer Hook in React?
The introduction of Hooks gave a new dimension to thinking about and using React applications. Hooks are simple JavaScript functions that help us to use state and other React features without writing a class. There are various Hooks available in React and out of those, there are mainly two Hooks that are used for state management: useState and useReducer Hooks.
Have you ever faced any difficulty in handling global states in a React app? Or when you need to manage complex data structures and perform side effects?
These concepts can be tricky and time-consuming. As we move through the course of this article, we will understand everything in detail about handling such cases while working on React application.
What comes to your mind whenever you notice the term 'reducer' in React?
The term reducer simply means to reduce! In technical terms, a reducer is a JavaScript function that takes the help of an action (passed as one of the parameters) to receive and determine the changes to an application's state. We can update a component's state object using Reducer and it works quite similarly to Redux in React.
Also the question arises, what is useReducer Hook in React?
useReducer Hook in React is used for complex state manipulations (where multiple states rely on complex logic) & state transitions. useReducer Hook is an alternative to useState Hook and we use it when the React components need to be optimized or when the next state value is dependent upon the previous state value. We can simply import useReducer from 'react' for using it, just like other React Hooks:
Pre-requisites
Before moving ahead and exploring more about the useReducer Hook in React, we must check the prerequisite list below and setup the development environment as per the requirements:
- npm or yarn installed in the system.
- Node.js installed (version >= 12.x.x)
- create-react-app CLI (Command-Line Interface) installed
- Knowledge of basics of React Hooks (useState, useEffect, etc.)
Syntax of UseReducer
Three arguments are passed to the useReducer hook including the reducer function, and the initial state object and the third argument is the function to load the initial state lazily.
Syntax for useReducer Hook in React:
- The reducer function contains the custom state logic and takes action & the current state as parameters. Action helps to receive and determine the changes to the application’s state.
- The initialState can be a single key-value pair but generally, in the case of useReducer Hook, it is an object of various key-value pairs.
- useReducer Hook returns the current state and the dispatch method.
- The returned state represents the current state, initially set to the initialState that we provide as an argument to the useReducer hook.
- The dispatch method is used to trigger an action in the reducer function with the help of array destructuring.
- Reducer function generally returns the next state dependent upon the current state. React store the next state, render the component, and update the UI.
Usage of UseReducer in React
As we have already discussed that the useReducer Hook takes in three arguments: the reducer function, the initial state, and the init function. The third argument i.e, the init function is optional but it is important to know its functionality and the usage purpose. The init function is used to load the initial state lazily.
In simple terms, if the third argument i.e, the init function is not specified, then the initial state is set to the initialState argument. Otherwise, the initial state is set to the result of calling init(initialState argument). In this manner, we can write the logic outside the reducer for extracting the initial state. It is also helpful for resetting the state value later by the response of action.
We need the initial state to be different in different situations and hence, instead of using only one actual state, we can create the initial state dynamically anywhere, and it will also override the initialState argument specified in the hook.
Syntax for the Third Argument in useReducer:
Example:
Let us now understand in detail how to use the useReducer hook and what is happening behind its functioning.
The initialState Argument
The initialState argument denotes the default value of the component's state when it gets mounted for the first time in the application. We can initialize the useReducer state by passing the initial state as the second argument:
Note that we can also use lazy initialization to initialize the useReducer state as discussed above using the third argument i.e, the init function.
The Reducer Function in React
Whenever any action occurs within the application, the component's state needs to be updated. The reducer function decides what state should contain based on action. It returns an object which is used to change the state of the component.
The reducer function takes in two arguments: the state and the action. The state is the application's current state and action is an object which contains all those details which we pass through the dispatch function.
- Action can contain important details like the new values which are needed to update the current state of the component.
- Action should include the minimal necessary information that the reducer needs to compute the next state.
In simple terms, the reducer function in React is used to calculate and return the updated next state.
How to Dispatch an Action?
As we have discussed above that the action is being dispatched using the dispatch function and now, we will see how actions are being dispatched. We have also noticed that the useReducer hook returns two values in an array where the first one is the state object and the second one is the dispatch function.
Dispatch function lets us update/change the state of the component and trigger a re-render. We have to pass the action as the only optional argument to the dispatch function. The next stage of React is decided upon the results returned from the reducer function based on the actions being dispatched.
Note that the dispatch functions do not have any return value.
Examples of UseReducer in React
Let us understand the concept of useReducer Hook practically using real examples!
Example 1: (Classic Counter)
In the below code example, we have edited the App.js file of React application wherein we have used useReducer Hook to implement classic counter functionality. useReducer hook in the below code accepts two arguments: the reducer function and the initial state value.
The initial state is an integer value, which starts from 0. The reducer function takes in two arguments: the current state and action where the action is of string type in this example. Note that we have used JSX in the function App() to return the counter component having two buttons (+ and -) to increase and decrease the counter value. On clicking these buttons in real-time, the dispatch function will be called which is usually used to change or replace the component's state value. We have also passed the action of type string i.e, type: 'INCREMENT'/'DECREMENT' in the dispatch function.
The action is passed to the reducer function as an argument where we have used switch statements for different action-type cases to change/replace and return the new component's state value.
Output: As we can observe in the output, we can dynamically click these increment and decrement buttons to increase and decrease the counter state value respectively.
Example 2: (Form object)
In the below code example, we have edited the App.js file of React application wherein we have used useReducer Hook to implement a form object with name and age as form fields. useReducer hook in the below code accepts two arguments: the reducer function and the initial state value object.
An initial state is an object with the name 'Mayank' and age as '21'. The reducer function takes in two arguments: the current state and action where the action is of string type in this example. Note that we have used JSX in the function App() to return the component. The component consists of an input box and increment & decrement age buttons. On clicking these buttons in real-time, the dispatch function will be called which is usually used to change or replace the component's state. We have also passed an action of type string i.e, type: 'increment'/'decrement'/'change' in the dispatch function.
Note that what we type in the input box will be treated as a target value and it will be passed in the action of type: 'change'. The action is passed to the reducer function as an argument where we have used switch statements for different action-type cases to change/replace and return the new component's state value.
Output: As we can observe in the output, we can dynamically click these increment and decrement buttons to increase and decrease the age state value respectively. Also what we type in the input box, will be reflected as the name whose age is specified.
Example 3: (List of todo items)
In the below code example, we have edited the App.js file of React application wherein we have used useReducer Hook to implement a list of to-do items. It is helpful to maintain a record of completed and pending tasks by checking/unchecking the items from the list. useReducer hook in the below code accepts two arguments: the reducer function and the initialState object.
The initial state is an array of objects declared outside the App() function with id, title, and check as key fields. Also, the reducer function takes in two arguments: the current state and action where the action is of string type in this example.
Note that we have used JSX in the function App() to return the list of to-do items using the JavaScript map function where the key is the id of the item. On clicking the checkbox of the items in real-time, the dispatch function will be called which is usually used to change or replace the component's state. We have also passed an action of type string i.e, type: 'CHECK' and id in the dispatch function.
The action is passed to the reducer function as an argument where we have used switch statements for different action-type cases to change/replace and return the new component's state value. Whenever we click any checkbox, its check field gets affected and if it is false in the reducer function while checking, the item will be unchecked otherwise it will be checked.
Output:
As we can observe in the output below, we have a list of 5 items wherein we can implement check/uncheck functionality to keep a track of pending and completed tasks.
Note that we can also add/delete more items and all this can be contained in a single useReducer hook by adding more actions.
When to Use UseReducer Instead of UseState Hook in React?
After exploring & learning about useReducer Hook in detail, you might face a dilemma i.e, when should you use React?useReducer hook instead of React.useState hook?
We should use React.useReducer Hook when:
- Application architecture is too complex and big in size.
- Multiple states rely on complex logic i.e, state value is an object or an array rather than a single value.
- We need state architecture that is easy to manage and maintain for our application.
- Logic to update state object is too complex or when we are handling global states in React.
On the contrary, we should use React.useState Hook when:
- Application architecture is simple and small in size.
- State value is a single primitive value rather than an array or an object.
- Simple UI state transitions and state manipulations.
- Logic is simple, less complicated, and resides only within one component.
FAQs
Q. After dispatching, a specific part of the reducer state becomes undefined
A. This is a very common issue that users face while working on React applications where they forget to copy existing fields of the state object in the case branch and return the new state. Without adding ...state in the example below, the next state returned by the reducer function would contain only the name field.
Q. After dispatching, the entire reducer state becomes undefined
A. After dispatching, if the entire reducer state becomes undefined, then there are two possibilities for this:
- Return state is missing in any of the cases.
- Action type doesn't match any of the case statements. We can identify the type of error by throwing the error statement outside the switch block:
Q. Action has been dispatched but the screen doesn't update
A. When the next state returned by the reducer function is equal to the previous state, the update will be ignored by React. It happens when we change the existing state object directly.
In the above example, we tried mutating the existing state object, and thus, React will ignore the update even if we return the state from the reducer function. To fix the above issue, we must ensure that we are replacing the objects in the state rather than mutating the existing state object.
Q. Action has been dispatched, but logging gives the old state value
A. States are like snapshots where updating the state renders the application with the new state value rather than affecting the actual state variable in an already running event handler.
For logging the next state, we can call the reducer function which returns the new state value:
Q. The reducer function is running two times unusually
A. In strict mode, the reducer function is called twice by React but it shouldn't break/affect the functionality of the code. React uses the result of one call and ignores the result of the second call. This development-specific behavior keeps the React components pure. Note that only the component, the initializer, and the reducer functions need to be pure and not the event handlers. Therefore, React never calls event handlers twice.
Q. Error: "Too many re-renders"
A. To prevent the infinite loop of renders, React limits the number of renders. When we dispatch an action unconditionally, React component enters a loop (render, dispatch, again render, again dispatch, and so on...). This usually happens due to a mistake in writing event handlers:
Turbocharge Your React Journey! Dive into Our Full Stack Courses with React-Expert Instructors. Enroll Now!
Conclusion
- useReducer Hook allows developers to handle complex state manipulations (handling multiple states that rely on complex logic) & updates.
- Syntax for useReducer: const [state, dispatch] = useReducer(reducer, initialState, init);
- Three arguments are taken by the useReducer hook including the reducer function, the initial state object, and the third argument is the function to load the initial state lazily.
- useReducer hook returns the current state and the dispatch method.
- The initialState argument denotes the default value of the component’s state when it gets mounted for the first time in the application.
- The reducer function contains the custom state logic and takes action & the current state as parameters.
- The reducer function generally returns the next state dependent upon the current state. React store the next state, render the component, and update the UI.
- Actions that are used in the reducer function, are being dispatched using the dispatch function.
- Dispatch function lets us update/change the state of the component and trigger a re-render. We have to pass the action as the only optional argument to the dispatch function.
- Actions help to receive and determine the changes to the application’s state.
- Action should include the minimal necessary information that the reducer needs to compute the next state.
- We should use the useReducer hook rather than the useState hook when multiple states rely on complex logic i.e, the state value is an object or an array rather than a single value.