
It is an alternative to using the useState hook and can be helpful when your component’s state logic becomes more complex and involves multiple actions.
Practical Example
// login.jsx
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
const emailReducer = (state, action) => {
console.log(action);
// checking action type
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.includes("@") };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.includes("@") };
}
// if action type not matched
return {
value: "",
isValid: false
};
};
const passwordReducer = (state, action) => {
console.log(action);
if (action.type === "USER_INPUT") {
return {
value: action.val,
isValid: action.val.trim().length > 6
};
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.trim().length > 6 };
}
return {
value: "",
isValid: false
};
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
// const [enteredPassword, setEnteredPassword] = useState("");
// const [passwordIsValid, setPasswordIsValid] = useState();
//above commented code replaced with useReducer
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: "",
isValid: null
});
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
value: "",
isValid: null
});
const { isValid: emailIsValid } = emailState;
const { isValid: passwordIsValid } = passwordState;
useEffect(() => {
const identifier = setTimeout(() => {
console.log("Checking form Validity");
setFormIsValid(emailState.isValid && passwordState.isValid);
}, 500);
return () => {
console.log("CLEANUP");
clearTimeout(identifier); // clearing timeout
};
}, [emailIsValid, passwordIsValid]); // Run every time enteredEmail and enteredPassword state changes
const emailChangeHandler = (event) => {
// setEnteredEmail(event.target.value);
dispatchEmail({
type: "USER_INPUT",
val: event.target.value
});
// setFormIsValid(event.target.value.includes("@") && passwordState.isValid);
};
const passwordChangeHandler = (event) => {
// setEnteredPassword(event.target.value);
dispatchPassword({
type: "USER_INPUT",
val: event.target.value
});
// setFormIsValid(emailState.isValid && event.target.value.trim().length > 6);
};
const validateEmailHandler = () => {
// setEmailIsValid(emailState.isValid);
dispatchEmail({
type: "INPUT_BLUR"
// val: event.target.value
});
};
const validatePasswordHandler = () => {
// setPasswordIsValid(enteredPassword.trim().length > 6);
dispatchPassword({
type: "INPUT_BLUR"
// val: event.target.value
});
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, passwordState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={passwordState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
Another Example
import React, { useReducer } from 'react';
// Step 1: Create the reducer function
const initialState = { count: 0 };
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
// Step 2: Use the useReducer hook to initialize state and get dispatch function
const [state, dispatch] = useReducer(counterReducer, initialState);
// Step 3: Define functions to dispatch actions
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
const decrement = () => {
dispatch({ type: 'DECREMENT' });
};
return (
<div>
<h1>Counter: {state.count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
export default Counter;
Adding Nested Properties As Dependencies To useEffect
In above example. We used object destructuring to add object properties as dependencies to useEffect().
const { someProperty } = someObject;
useEffect(() => {
// code that only uses someProperty ...
}, [someProperty]);
This is a very common pattern and approach, which is why I typically use it and why I show it here (I will keep on using it throughout the course).
I just want to point out, that they key thing is NOT that we use destructuring but that we pass specific properties instead of the entire object as a dependency.
We could also write this code and it would work in the same way.
useEffect(() => {
// code that only uses someProperty ...
}, [someObject.someProperty]);
This works just fine as well!
But you should avoid this code:
useEffect(() => {
// code that only uses someProperty ...
}, [someObject]);
Why?
Because now the effect function would re-run whenever ANY property of someObject changes – not just the one property (someProperty in the above example) our effect might depend on.
useReducer Vs useState

Credits: https://www.udemy.com/course/react-the-complete-guide-incl-redux/
Use Reducer using TypeScript
import { useReducer } from 'react';
type StateType = {
count: number;
};
type ActionType =
| { type: 'INCREMENT'; payload: number }
| { type: 'DECREMENT'; payload: number };
const reducer = (state: StateType, action: ActionType): StateType => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + action.payload };
case 'DECREMENT':
return { count: state.count - action.payload };
default:
return state;
}
};
const initialState: StateType = {
count: 0,
};
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const incrementHandler = (): void => {
dispatch({
type: 'INCREMENT',
payload: 1,
});
};
const decrementHandler = (): void => {
dispatch({
type: 'DECREMENT',
payload: 1,
});
};
return (
<>
<h1>Count Changes</h1>
<div>
<p>Count: {state.count}</p>
<div>
<button onClick={incrementHandler}>+</button>
<button onClick={decrementHandler}>-</button>
</div>
</div>
</>
);
}
export default App;
Check this example: https://stackblitz.com/edit/vitejs-vite-5xrzgk?embed=1&file=src%2FApp.tsx