React hooks: work with useState and useReducer effectively

What is React Hooks?
React Hooks are special functions that add state, lifecycle features, performance optimization, and more to functional components. There are 10 built-in functions with names beginning with "use," each with unique functionality and use cases. They are completely optional and support incremental adoption.
React Hooks are functions that accept arguments and return values. You can also create custom hooks. However, you must follow specific rules to avoid confusion and unexpected results.
Two rules for using hooks
- Only call hooks at the top level — not within conditionals, loops, or nested functions
- Only call hooks from React functions (functional components and custom hooks)
To enforce these rules, add the eslint-plugin-react-hooks linter plugin to your project.
Common React Hooks
- useState: enables functional components to have local state through a simple API
- useReducer: organizes state management similarly to Redux
- useEffect: handles side effects (combines
componentDidMount,componentDidUpdate, andcomponentWillUnmount) - useContext: passes data without prop drilling or third-party libraries
- useCallback: improves performance by memoizing callbacks
- useMemo: caches data to save computation time
The useState hook
The useState hook allows functional components to manage local state. It returns an array with two elements: the current state value and a function to update it.
Basic usage:
const [extraInfo, setExtraInfo] = useState(false);
extraInfo: the current state value (getter)setExtraInfo: function to update state (setter)
You can name variables anything, but convention dictates using "set" as a prefix for the setter function in camelCase.
Key differences from setState:
Unlike class component setState, useState replaces the old state entirely rather than merging it. The React team recommends using multiple useState calls for unrelated state properties rather than a single object, as it's easier to manage individual variables than object properties.
When to use multiple useState:
If you end up with many state variables, consider using a custom hook or useReducer instead.
The useReducer hook
The useReducer hook provides an organized way to manage complex state logic. It's essentially an enhanced version of useState and follows Redux patterns.
What is a reducer?
A reducer is a pure function accepting two parameters: the previous state and an action. It returns a new state based on the action type.
Basic usage:
const [state, dispatch] = useReducer(reducerFunction, initialState);
state: the current state objectdispatch: a function that sends action definitions to the reducerreducerFunction: the reducer function handling state updatesinitialState: the initial state (can be any data type, not just objects)
Example: login form with useReducer
When managing multiple related state properties (username, password, isLoading, error, etc.), useReducer groups them logically and makes the code more maintainable.
Action structure:
Actions are objects containing a type property and optional additional properties:
dispatch({ type: 'field', fieldName: 'password', payload: value });
Reducer function:
The reducer processes actions and returns a new state:
return { ...state, [action.fieldName]: action.payload };
Important: always spread the previous state (...state) before updating properties, as useReducer replaces rather than merges state.
Rules for reducers
Your reducer function must be pure and side-effect-free. Never make HTTP requests within a reducer — handle asynchronous operations elsewhere.
When to use useState vs useReducer
Choose based on your needs:
- Use useState for unrelated or independent state properties
- Use useReducer for multiple related properties with different update logic, or when one state property depends on another
As Kent C. Dodds noted: "Any time you need state x to update state y, that's an insta-useReducer use case."
Conclusion
React Hooks solve common problems developers encounter and encourage functional component usage for simplicity and efficiency. Both useState and useReducer provide powerful, easy-to-understand approaches to state management in functional components, accommodating all use cases.