Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside components in a single function, called a reducer. React reducers are functions that specify how the application’s state should change in response to actions dispatched to the reducer.
Reducers are functions responsible for managing changes to a piece of state. It takes two arguments: the current state and an action object that describes the type of change to be made. The reducer then calculates the new state based on the action type and returns it.
Understanding how reducers work
Reducer
A reducer is a pure function that takes the current state and an action as arguments and returns the new state based on the action.
Example
const reducer = (state: IState, action: { type: string }) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
};
Inside the reducer function, a switch statement or if-else chain is often used to determine how to update the state based on the action type. The reducer returns the new state after applying the specified changes.
Action
An action is an object that describes the type of change to be made to the state. It usually contains a type
property that specifies the action type and optionally a payload
property with additional data. An action type is a textual identifier that represents a specific operation or change that needs to be performed in the application state. The payload contains additional data related to the action which is necessary to perform the action or provide context for the operation.
Example
const actionIncrement = { type: "INCREMENT", payload: { count: 0 } };
Dispatch
Actions are dispatched to the reducer using the dispatch
function. This triggers the reducer to process the action and update the state accordingly.
Example
const increment = () => dispatch(actionIncrement);
useReducer hook
The useReducer
hook is used to manage state with reducers. It takes the reducer function and an initial state as arguments, and returns the current state and a dispatch function.
Example
const [state, dispatch] = useReducer(reducer, initialState);
Full example
reducer.ts
// State data structure
interface IState {
count: number;
}
// Initial state
export const initialState: IState = { count: 0 };
// Reducer function
export const reducer = (state: IState, action: { type: string }) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
};
Counter.tsx
import { useReducer } from "react";
import { initialState, reducer } from "./reducer";
export default function CounterDemo() {
// Initialize state using useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// Actions
const actionIncrement = { type: "INCREMENT" };
const actionDecrement = { type: "DECREMENT" };
// Dispatch actions
const increment = () => dispatch(actionIncrement);
const decrement = () => dispatch(actionDecrement);
return (
<div>
<button onClick={decrement}>-</button>
<span>{state.count}</span>
<button onClick={increment}>+</button>
</div>
);
}
You can simplify the previous code like this.
Counter.tsx
import { useReducer } from "react";
import { initialState, reducer } from "./reducer";
export default function CounterDemo() {
// Initialize state using useReducer
const [state, dispatch] = useReducer(reducer, initialState);
const increment = () => dispatch({ type: "INCREMENT" });
const decrement = () => dispatch({ type: "DECREMENT" });
return (
<div>
<button onClick={decrement}>-</button>
<span>{state.count}</span>
<button onClick={increment}>+</button>
</div>
);
}
or like this
Counter.tsx
import { useReducer } from "react";
import { initialState, reducer } from "./reducer";
export default function CounterDemo1() {
// Initialize state using useReducer
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
</div>
);
}