• React
    • JavaScript
    • Hooks

    React Hooks Explained for Beginners

    By Published Updated 5 min read

    A beginner's guide to React Hooks: useState, useEffect, useMemo, useCallback and useRef, the rules of hooks, and when each is worth using.

    React Hooks are functions, such as useState and useEffect, that let function components hold state and run side effects without writing a class. They became stable in React 16.8, released in February 2019 (React v16.8 release notes). This guide explains the hooks beginners meet first, useState, useEffect, useMemo, useCallback and useRef, and when each one is worth using.

    Key takeaways

    • useState stores values that should trigger a re-render when they change.
    • useEffect synchronizes a component with something outside React, and its dependency array controls when it runs again.
    • useMemo caches a calculated value and useCallback caches a function; both are optimizations, not requirements.
    • useRef keeps a mutable value between renders without causing a re-render.
    • Call hooks only at the top level of a component or custom hook, never inside loops or conditions.

    Why were hooks added to React?

    Before React 16.8, if you wanted state (like a counter) in a React component, you had to write a class component. Classes were verbose, and they could be confusing (this.handleClick = this.handleClick.bind(this), anyone?).

    Then came Hooks. Hooks let you use state and other React features inside plain functions. Today, the official React docs teach function components with hooks as the standard way to write components.

    What are the rules of hooks?

    React relies on hooks being called in the same order on every render, so there are two rules (React docs: rules of hooks):

    • Call hooks only at the top level of your component, not inside loops, conditions or nested functions.
    • Call hooks only from React function components or custom hooks, not from regular JavaScript functions.

    1. useState: How do you store data?

    useState returns a pair: the current value and a function that updates it. Calling the setter tells React to re-render the component with the new value.

    import { useState } from "react";
    
    export default function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <button onClick={() => setCount(count + 1)}>
          Clicked {count} times
        </button>
      );
    }
    

    If the initial value is expensive to compute, pass a function instead of a value. React useState lazy initialization explained shows why that matters.


    2. useEffect: How do you run side effects?

    useEffect runs code after React has rendered and updated the page. Use it to synchronize with things outside React, such as timers, subscriptions, network connections or document.title (React docs: useEffect). It covers the work class components did in componentDidMount, componentDidUpdate and componentWillUnmount.

    Syntax:

    useEffect(() => {
      // Setup: runs after the component renders
      console.log("Effect ran");
    
      return () => {
        // Cleanup: runs before the effect runs again, and when the component unmounts
        console.log("Cleaning up");
      };
    }, [dependency]);
    

    How does the dependency array work?

    • No array: The effect runs after every render. If it also updates state, you get an infinite loop.
    • [] (empty): The effect runs once, after the component mounts. In development Strict Mode, React runs an extra setup and cleanup cycle to help you catch missing cleanup code.
    • [prop]: The effect runs after mount and again whenever prop changes.

    Common mistake: Using an object or array created during render as a dependency. In JavaScript, { id: 1 } !== { id: 1 } because they are different references. A new object on every render makes the effect run after every render, and if that effect sets state, it loops forever. Depend on primitive values such as options.id instead, or create the object inside the effect.


    3. useMemo: How do you cache a calculation?

    useMemo caches the return value of a calculation between renders and recalculates it only when its dependencies change.

    Imagine you have a list of 10,000 users and want to show only the active ones. You don't want to re-filter that list every time the component re-renders for an unrelated reason, such as a click on another button.

    // Without useMemo: filter runs on every render
    const visibleUsers = users.filter((u) => u.active);
    
    // With useMemo: filter runs again only when users changes
    const memoizedVisibleUsers = useMemo(() => {
      return users.filter((u) => u.active);
    }, [users]);
    

    When should you use it? Measure first. The React docs suggest wrapping the calculation in console.time() and console.timeEnd(), and considering memoization if it takes a significant amount of time, such as 1ms or more (React docs: useMemo). Typical candidates:

    • Filtering or sorting large lists.
    • Transforming large datasets, for example to feed a chart.

    When should you skip it? For simple calculations like a + b. Storing the result and comparing dependencies buys you nothing there and makes the code harder to read.


    4. useCallback: How do you keep a function stable?

    useCallback caches the function definition itself, so the component gets the same function reference between renders until a dependency changes.

    // A new function is created on every render
    const handleClick = () => {
      console.log(id);
    };
    
    // The same function is reused until id changes
    const stableHandleClick = useCallback(() => {
      console.log(id);
    }, [id]);
    

    This matters in two situations:

    • Memoized children: If you pass a function to a child wrapped in memo, a new function on every render looks like a changed prop, so the child re-renders anyway and the optimization is lost.
    • Effect dependencies: If an effect depends on a function, a stable reference stops the effect from re-running on every render.

    5. useRef: How do you remember a value without re-rendering?

    useRef returns an object with a current property that persists across renders. Changing ref.current does not trigger a re-render, which makes refs useful for DOM elements and for values such as timer IDs.

    import { useRef } from "react";
    
    export default function FocusInput() {
      const inputRef = useRef(null);
    
      return (
        <>
          <input ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus the input</button>
        </>
      );
    }
    

    Summary cheat sheet

    HookReturnsPurposeUse case
    useState[value, setValue]Store data that affects renderingCounters, form inputs
    useEffectNothingSync with the outside worldTimers, subscriptions, document title
    useMemoThe cached valueSkip expensive recalculationsFiltering large lists
    useCallbackThe cached functionKeep function references stableProps for memoized children
    useRef{ current } objectPersist a value without re-renderingDOM access, timer IDs

    Try these hooks in the React playground: each code example above can be pasted in as the App component. When you need to reset a component's state completely, read React key prop vs state: reset a component.

    Frequently asked questions

    When were React Hooks introduced?

    Hooks became stable in React 16.8, released on February 6, 2019. That release included a stable implementation of hooks for React DOM, React DOM Server, React Test Renderer and React Shallow Renderer. Before then, state and lifecycle methods required class components, which is why older tutorials and codebases still use classes.

    Can I use hooks inside class components?

    No. Hooks work only inside function components and custom hooks. Class components keep using this.state and lifecycle methods. You can still mix both styles in one application, so an existing class-based codebase can adopt hooks gradually by writing new components as functions without rewriting the older ones.

    What is the difference between useMemo and useCallback?

    useMemo calls the function you pass and caches its return value, while useCallback caches the function itself without calling it. In other words, useCallback(fn, deps) behaves like useMemo(() => fn, deps). Use useMemo for expensive calculations and useCallback for stable function references passed to memoized children or effects.

    Why does my useEffect run twice?

    In development, React Strict Mode mounts each component, unmounts it and mounts it again, so your effect runs setup, cleanup and setup once more. This is intentional and helps reveal effects that are missing cleanup logic. It does not happen in production builds, so fix the cleanup instead of removing Strict Mode.

    Try the examples yourself

    Open the React playground to run and edit code from this article in your browser. There is nothing to install and no account to create.