KRYZO.

· React guide · 9 min read

React Hooks Explained

Hooks let function components hold state and react to changes. Learn the two you will use every day.

React component code showing useState and useEffect hooks

Before hooks, only class components could hold state, and logic was split across lifecycle methods with names like componentDidMount. Hooks let plain functions do the same job, and they let you group related logic together instead of spreading it across a component.

useState holds a value between renders

When a component re-renders, its local variables are recalculated. useState gives you a value that survives those re-renders and a function to update it. Calling the updater tells React to render the component again with the new value.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

The array destructuring reads clearly: the first item is the current value and the second is the setter. Never change the value directly; always go through the setter so React knows to re-render.

useEffect runs side effects

Rendering should be pure, so anything that reaches outside the component, such as fetching data or subscribing to events, belongs in useEffect. React runs the effect after the render completes.

import { useEffect, useState } from "react";

function UserName({ id }) {
  const [name, setName] = useState("");

  useEffect(() => {
    let active = true;
    fetch(`/api/users/${id}`)
      .then((response) => response.json())
      .then((user) => {
        if (active) setName(user.name);
      });
    return () => {
      active = false;
    };
  }, [id]);

  return <p>{name}</p>;
}

The dependency array decides when the effect re-runs. Listing [id] means it runs once on mount and again whenever id changes. An empty array runs it once, and omitting the array runs it after every render.

Cleaning up effects

Some effects start something that must be stopped: a timer, a subscription or an in-flight request. Return a function from the effect to clean up, and React calls it before the next run and when the component unmounts.

The active flag above is a simple version of this. It prevents an old request from writing state after the component has moved on, which is a common source of confusing bugs.

The rules of hooks

Call them at the top level

Never call a hook inside a condition, loop or nested function. Order must be identical on every render.

Only from functions React knows

Call hooks from components or from your own custom hooks, never from an ordinary helper function.

Name custom hooks with use

Starting a function name with use lets React and linters treat it as a hook.

React relies on the order of hook calls to match state with a component. Branches that sometimes call a hook break that order, which is why the rules exist.

Building a small custom hook

When the same logic appears in several components, extract it into a custom hook. It is just a function whose name starts with use and that may call other hooks.

import { useEffect, useState } from "react";

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  return width;
}

Any component can now call useWindowWidth() and receive the current width. The component stays focused on rendering, and the reusable behaviour lives in one tested place.

useMemo and useCallback

These two hooks cache a value or a function between renders so that unchanged dependencies do not trigger extra work. Use them when a calculation is genuinely expensive or when a stable reference is required by a memoised child. Reaching for them everywhere adds complexity without a real gain.

Back to all blogs