React hooks: work with useState and useReducer effectively

·4 min read·Originally published on Medium

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

  1. Only call hooks at the top level — not within conditionals, loops, or nested functions
  2. 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, and componentWillUnmount)
  • 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 object
  • dispatch: a function that sends action definitions to the reducer
  • reducerFunction: the reducer function handling state updates
  • initialState: 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.