Beautiful Next.js blog starters

Technology

Understanding React Hooks: A Visual Guide for Beginners

React Hooks explained through analogies, flip cards, and side-by-side comparisons. No prior React experience needed.

Your Name
January 20, 2026
5 min read
Understanding React Hooks: A Visual Guide for Beginners
What You'll Learn
  • Hooks let function components access React features previously only available in class components
  • `useState` gives your component memory between renders
  • `useEffect` runs code after rendering — for data fetching, timers, and subscriptions
  • Always call hooks at the top level, never inside conditions or loops
  • Always clean up effects that set up listeners or timers

React Hooks were introduced in 2019 and fundamentally changed how React developers write components. Before hooks, you had to use class-based components whenever you needed features like state or lifecycle methods. Now you can write simpler functions that do everything. If you have tried to learn hooks and found the documentation confusing, this guide takes a different approach.

What Is a Hook, Actually?

A hook is a special function that starts with the word use. When you call a hook inside a React component, it "hooks into" React's internal systems — letting your simple function access features that were previously only available to class components.

Think of it like a plug-in. Your component is a lamp. Hooks are the outlet. Without the outlet, the lamp does nothing. With the right hook, your component can remember things, talk to external APIs, and respond to changes in the page.

Flash Card
Question
What is useState?
tap to flip ↺
Answer
A hook that lets your component remember values between renders — like a sticky note your component can update.
Flash Card
Question
What is useEffect?
tap to flip ↺
Answer
A hook that runs code after your component renders — perfect for fetching data, setting up timers, or syncing with external systems.

The Two Hooks You Will Use Most

useState: Making Components Remember

Without useState, your component forgets everything every time React re-renders it. Variables reset. Counters go back to zero. useState is the solution.

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

The syntax looks unusual at first. useState(0) returns an array with two things: the current value and a function to update it. The [count, setCount] part unpacks that array. You name them whatever makes sense.

useEffect: Running Code at the Right Moment

useEffect runs after your component appears on screen. It is where you fetch data, start timers, or do anything that needs to happen after rendering.

import { useState, useEffect } from 'react';
 
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
 
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // Re-runs whenever userId changes
 
  if (!user) return <p>Loading...</p>;
  return <p>Hello, {user.name}</p>;
}

The array at the end — [userId] — is called the dependency array. It tells React when to re-run the effect. If userId changes, the effect runs again and fetches new data.

Before and After Hooks

The best way to appreciate hooks is to see what code looked like before them.

Without Hooks (Class Component)
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
 
  render() {
    return (
      <button onClick={() =>
        this.setState({
          count: this.state.count + 1
        })
      }>
        {this.state.count}
      </button>
    );
  }
}

More boilerplate. Requires understanding this. Constructor needed for any state. Harder to extract logic.

VS
With Hooks (Function Component)
function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <button onClick={() =>
      setCount(count + 1)
    }>
      {count}
    </button>
  );
}

Shorter. No this confusion. No constructor. Logic is easy to extract into custom hooks. Reads top to bottom.

The functional version with hooks is not just shorter — it is genuinely easier to understand and test.

The Rules of Hooks

Key Point: Hooks have two rules that you must never break. Break them and React will silently behave in confusing ways that are very hard to debug.
Hook Rules

Rule 1: Only call hooks at the top level. Never inside if statements, loops, or nested functions. React tracks hooks by call order — if that order changes between renders, everything breaks.

Rule 2: Only call hooks inside React functions. Hooks only work inside React component functions or inside other custom hooks. Never in regular JavaScript functions.

Why these rules? React keeps a list of hook values in the order you call them. If you call useState inside an if block, React might skip it on some renders, shifting every hook that comes after it and breaking the whole component.

Custom Hooks: Sharing Logic Between Components

One of the most powerful features of hooks is that you can create your own. A custom hook is just a function that starts with use and calls other hooks inside it.

// Custom hook: useWindowWidth
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
 
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
 
  return width;
}
 
// Using it in any component:
function ResponsiveComponent() {
  const width = useWindowWidth();
  return <p>Window is {width}px wide</p>;
}

Notice the return statement inside useEffect — that is the cleanup function. It runs when the component is removed from the page, preventing memory leaks. Whenever you set up a listener or timer in useEffect, always clean it up in a return function.

Tip: Name custom hooks by what they return or do, not by how they work internally. useWindowWidth tells you the result. useResizeEventListener describes the implementation. The first name is more useful when reading components.

Key Takeaways

  • Hooks let function components access React features previously only available in class components
  • useState gives your component memory between renders
  • useEffect runs code after rendering — for data fetching, timers, and subscriptions
  • Always call hooks at the top level, never inside conditions or loops
  • Always clean up effects that set up listeners or timers
  • Custom hooks (starting with use) let you share logic between components without duplicating code

Finished reading? Mark this article to track your progress.

Share:

Your Name

We build beautiful, production-ready Next.js starters.