Fundamentals of Reducer Functions in React
A reducer function is a central piece in managing state when your application's state logic becomes complex. It is based on the principle of "state + action → new state", meaning the reducer determines the new state of your application based on the current state and an action.
Principles of Reducer Functions
-
Pure Function:
A reducer is a pure function, meaning:- It doesn't mutate the original state.
- It only calculates and returns the new state.
- The same inputs always produce the same output.
-
Centralized Logic:
All state changes are handled in one place, making your code more organized and readable. -
Action-Driven Updates:
Reducers use an action object ({ type, payload }
) to describe the type of update and the data needed for the update. -
Immutable State:
Reducers return a new state object without directly modifying the old state.
Step-by-Step Guide with a Fully Functional App
We’ll build a simple counter app that:
- Displays a count.
- Allows incrementing, decrementing, and resetting the count.
Step 1: Project Setup
Create a new React app:
npx create-react-app reducer-counter-app
cd reducer-counter-app
Step 2: Define the Reducer Function
Create a file counterReducer.js
:
// counterReducer.js
export const counterReducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
};
Explanation:
- State: The current state of the counter (e.g.,
{ count: 0 }
). - Action: An object with a
type
(e.g.,"INCREMENT"
) and optional data (payload
). - Switch Statement: Handles different action types to determine the new state.
Step 3: Use the Reducer in a Component
Modify App.js
to use the reducer:
import React, { useReducer } from "react";
import { counterReducer } from "./counterReducer";
const App = () => {
const initialState = { count: 0 }; // Initial state for the counter
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<h1>Counter: {state.count}</h1>
{/* Increment Button */}
<button
onClick={() => dispatch({ type: "INCREMENT" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Increment
</button>
{/* Decrement Button */}
<button
onClick={() => dispatch({ type: "DECREMENT" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Decrement
</button>
{/* Reset Button */}
<button
onClick={() => dispatch({ type: "RESET" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Reset
</button>
</div>
);
};
export default App;
How It Works
-
State Initialization:
initialState = { count: 0 }
.- The
useReducer
hook initializesstate
withinitialState
.
-
Dispatch Function:
dispatch({ type: "INCREMENT" })
sends an action to the reducer.- The reducer calculates the new state (
{ count: count + 1 }
) and returns it.
-
Reducer Logic:
- The reducer handles the
"INCREMENT"
,"DECREMENT"
, and"RESET"
actions in a centralized way.
- The reducer handles the
Step 4: Add Styles and Clean Up
To improve readability and styling, add CSS directly to the div
or create a CSS file.
Complete Example Code
counterReducer.js
export const counterReducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
};
App.js
import React, { useReducer } from "react";
import { counterReducer } from "./counterReducer";
const App = () => {
const initialState = { count: 0 };
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<h1>Counter: {state.count}</h1>
<button
onClick={() => dispatch({ type: "INCREMENT" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Increment
</button>
<button
onClick={() => dispatch({ type: "DECREMENT" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Decrement
</button>
<button
onClick={() => dispatch({ type: "RESET" })}
style={{ margin: "5px", padding: "10px 20px", fontSize: "16px" }}
>
Reset
</button>
</div>
);
};
export default App;
Why This Is Better Than useState
-
Centralized Logic:
- Instead of writing logic in multiple places, the reducer handles all state changes in one place.
-
Readability:
- The reducer makes state transitions explicit and predictable, improving maintainability.
-
Scalability:
- Adding more actions (like
"DOUBLE_COUNT"
) only requires updates to the reducer, not scattered logic across components.
- Adding more actions (like
Let me know if you'd like to extend this example further with multiple contexts or advanced scenarios! 😊