You are viewing a preview of this course. Sign in to start learning

Lesson 1: Introduction to React Hooks

Learn the fundamentals of React Hooks, including useState and useEffect, to build modern functional components with state and side effects.

๐ŸŽฃ Introduction to React Hooks

Welcome to the world of React Hooks! If you've been learning React, you might have heard that Hooks are a game-changer. But what exactly are they, and why should you care? Let's dive in and discover how Hooks make your React code cleaner, simpler, and more powerful.

๐Ÿค” What Are React Hooks?

Imagine you're building with LEGO blocks. In the old days of React, you had two types of blocks: class components (big, complex blocks with lots of features) and functional components (small, simple blocks that could only display things). If you wanted to add features like memory (state) or timers (side effects) to your simple blocks, you had to rebuild them as complex blocks. That was frustrating!

React Hooks are special tools that let you add superpowers to your simple functional components without converting them into class components. Think of Hooks as magical attachments you can click onto your LEGO blocks to give them new abilities.

๐Ÿ’ก Key Point: Hooks are functions that let you "hook into" React features like state and lifecycle methods from functional components.

๐ŸŽฏ Why Were Hooks Invented?

Before Hooks (introduced in React 16.8 in February 2019), developers faced several challenges:

  1. Complex Class Components: Writing this.state, this.setState, and this.props everywhere was tedious and error-prone
  2. Confusing Lifecycle Methods: Logic was scattered across componentDidMount, componentDidUpdate, and componentWillUnmount
  3. Hard to Reuse Logic: Sharing stateful logic between components required complex patterns like "render props" or "higher-order components"
  4. Confusing this Keyword: Binding event handlers and understanding this confused many developers

Hooks solved all these problems! ๐ŸŽ‰

๐Ÿ—๏ธ The Architecture of Hooks

Let's visualize how Hooks fit into React:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚     Functional Component            โ”‚
โ”‚                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚   useState Hook              โ”‚  โ”‚
โ”‚  โ”‚   (manages state)            โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚   useEffect Hook             โ”‚  โ”‚
โ”‚  โ”‚   (handles side effects)     โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚   Other Hooks...             โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                     โ”‚
โ”‚         โ†“  โ†“  โ†“                     โ”‚
โ”‚      Returns JSX                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“‹ The Rules of Hooks

Before we dive deeper, you must understand these two golden rules:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      THE RULES OF HOOKS               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1. Only call Hooks at the TOP LEVEL   โ”‚
โ”‚    โŒ Don't call inside loops          โ”‚
โ”‚    โŒ Don't call inside conditions     โ”‚
โ”‚    โŒ Don't call inside nested funcs   โ”‚
โ”‚                                        โ”‚
โ”‚ 2. Only call Hooks from:               โ”‚
โ”‚    โœ… React functional components      โ”‚
โ”‚    โœ… Custom Hooks                     โ”‚
โ”‚    โŒ Regular JavaScript functions     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ’ก Why these rules? React relies on the order Hooks are called to track state correctly. If you call Hooks conditionally, the order might change between renders, breaking your app!

๐ŸŽจ Core Hook #1: useState

The useState Hook is your bread and butter. It lets functional components remember values between renders.

The Anatomy of useState

const [value, setValue] = useState(initialValue);

Let's break this down:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  const [count, setCount] = useState(0);     โ”‚
โ”‚         โ†‘       โ†‘            โ†‘              โ”‚
โ”‚         โ”‚       โ”‚            โ”‚              โ”‚
โ”‚      current  function   initial            โ”‚
โ”‚       value   to update  value              โ”‚
โ”‚               the value                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

What's happening?

  • useState(0) returns an array with two items
  • We use array destructuring to grab both items at once
  • First item: the current state value
  • Second item: a function to update that value
  • The 0 is the initial value when the component first renders

๐Ÿง  Memory Tip: Think "use-state" as "use some memory to remember this value"

How useState Works Behind the Scenes

When you call setCount(5), React:

  1. Updates the state value
  2. Schedules a re-render of your component
  3. On the next render, count will have the new value
  4. Your component function runs again with the updated value
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Initial Render                          โ”‚
โ”‚  count = 0                               โ”‚
โ”‚  User clicks button                      โ”‚
โ”‚         โ†“                                โ”‚
โ”‚  setCount(1) is called                   โ”‚
โ”‚         โ†“                                โ”‚
โ”‚  React schedules re-render               โ”‚
โ”‚         โ†“                                โ”‚
โ”‚  Second Render                           โ”‚
โ”‚  count = 1                               โ”‚
โ”‚  UI updates with new value               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โšก Core Hook #2: useEffect

The useEffect Hook lets you perform side effects in functional components.

๐Ÿค” What's a side effect? Anything that affects something outside the component:

  • Fetching data from an API
  • Setting up subscriptions
  • Manually changing the DOM
  • Setting timers
  • Logging to the console

The Anatomy of useEffect

useEffect(() => {
  // Your side effect code here
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]);

Breaking it down:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  useEffect(() => {                            โ”‚
โ”‚    document.title = `Count: ${count}`;        โ”‚
โ”‚  }, [count]);                                 โ”‚
โ”‚      โ†‘             โ†‘                          โ”‚
โ”‚   effect       dependency                     โ”‚
โ”‚   function      array                         โ”‚
โ”‚                                               โ”‚
โ”‚  "Run this effect whenever 'count' changes"   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Dependency Array: The Brain of useEffect

The dependency array is crucial for understanding useEffect:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Dependency Array Behavior                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  useEffect(() => {...}, [a, b])             โ”‚
โ”‚  โ†’ Runs when 'a' or 'b' changes             โ”‚
โ”‚                                             โ”‚
โ”‚  useEffect(() => {...}, [])                 โ”‚
โ”‚  โ†’ Runs ONCE after first render             โ”‚
โ”‚     (empty array = no dependencies)         โ”‚
โ”‚                                             โ”‚
โ”‚  useEffect(() => {...})                     โ”‚
โ”‚  โ†’ Runs after EVERY render                  โ”‚
โ”‚     (no array = always run)                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ’ก Critical Insight: The dependency array tells React "re-run this effect only if these values changed." This prevents unnecessary work and infinite loops!

๐Ÿ“š Detailed Examples with Explanations

Example 1: Counter with useState ๐Ÿ”ข

Let's build a simple counter to understand useState in action:

import React, { useState } from 'react';

function Counter() {
  // Declare a state variable called 'count'
  // Initialize it with 0
  const [count, setCount] = useState(0);

  // Function to increment the count
  const increment = () => {
    setCount(count + 1);
  };

  // Function to decrement the count
  const decrement = () => {
    setCount(count - 1);
  };

  // Function to reset to zero
  const reset = () => {
    setCount(0);
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increase +</button>
      <button onClick={decrement}>Decrease -</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default Counter;

What's happening step-by-step:

  1. Line 4: We call useState(0) which gives us count (current value: 0) and setCount (updater function)
  2. Line 8: When increment is called, setCount(count + 1) tells React "update count to current value + 1"
  3. Line 13: Similarly for decrement
  4. Line 18: Reset sets count back to 0
  5. Line 24: {count} displays the current value
  6. Line 25-27: Each button has an onClick that calls our functions

๐Ÿ”ง Try this: What happens if you click increment 3 times quickly? React batches the updates efficiently!

Example 2: Document Title with useEffect ๐Ÿ“„

Let's make the browser tab title show our count:

import React, { useState, useEffect } from 'react';

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

  // This effect runs whenever 'count' changes
  useEffect(() => {
    // Update the document title
    document.title = `You clicked ${count} times`;
    
    // Log to see when effect runs
    console.log('Effect ran! Count is now:', count);
  }, [count]); // Only re-run if count changes

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Click me!
      </button>
      <p>Check the browser tab title!</p>
    </div>
  );
}

export default CounterWithTitle;

Flow of execution:

1. Component renders (count = 0)
   โ†’ Document title = "You clicked 0 times"
   
2. User clicks button
   โ†’ setCount(1) is called
   
3. React re-renders component (count = 1)
   โ†’ useEffect sees count changed from 0 to 1
   โ†’ Effect runs again
   โ†’ Document title = "You clicked 1 times"

Why use [count] in the dependency array?

  • Without it: Effect runs after every render (wasteful!)
  • With [count]: Effect only runs when count actually changes (efficient!)

Example 3: Data Fetching with useEffect ๐ŸŒ

A real-world pattern: fetching data when the component loads:

import React, { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Fetch data when component mounts
    fetch('https://api.example.com/user/1')
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []); // Empty array = run once on mount

  if (loading) return <p>Loading user data...</p>;
  if (error) return <p>Error: {error}</p>;
  if (!user) return <p>No user found</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
}

export default UserProfile;

Breaking down this pattern:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Three State Pattern                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  1. user: null โ†’ holds fetched data    โ”‚
โ”‚  2. loading: true โ†’ tracks if fetching โ”‚
โ”‚  3. error: null โ†’ captures errors      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Why [] for dependencies?

  • Empty array means "run this effect once when component first appears"
  • Perfect for fetching initial data
  • Without [], it would fetch on every render (infinite loop!)

๐ŸŽฏ Real-world tip: This pattern is so common that many developers use libraries like React Query or SWR to handle it automatically!

Example 4: Cleanup with useEffect ๐Ÿงน

Some effects need cleanup (timers, subscriptions, listeners). Here's how:

import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return; // Don't set up timer if not running

    // Set up the timer
    const intervalId = setInterval(() => {
      setSeconds(s => s + 1); // Using functional update
    }, 1000);

    // Cleanup function: runs when component unmounts
    // or before effect runs again
    return () => {
      clearInterval(intervalId);
      console.log('Timer cleaned up!');
    };
  }, [isRunning]); // Re-run when isRunning changes

  return (
    <div>
      <h1>Seconds: {seconds}</h1>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Pause' : 'Start'}
      </button>
      <button onClick={() => setSeconds(0)}>Reset</button>
    </div>
  );
}

export default Timer;

Why cleanup matters:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Without cleanup:                         โ”‚
โ”‚  Component unmounts โ†’ timer keeps running โ”‚
โ”‚  โ†’ Memory leak! ๐Ÿ’ฅ                        โ”‚
โ”‚                                           โ”‚
โ”‚  With cleanup:                            โ”‚
โ”‚  Component unmounts โ†’ cleanup runs        โ”‚
โ”‚  โ†’ clearInterval() stops timer โœ…         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Notice setSeconds(s => s + 1)?

  • This is a functional update
  • Instead of using the current seconds value, we use a function
  • React passes the current value as s
  • Safer when updates depend on previous value

โš ๏ธ Common Mistakes and How to Avoid Them

Mistake 1: Forgetting the Dependency Array

โŒ WRONG:
useEffect(() => {
  document.title = `Count: ${count}`;
}); // Missing dependency array!

// This runs after EVERY render, even when count doesn't change
โœ… CORRECT:
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]); // Effect only runs when count changes

Mistake 2: Mutating State Directly

โŒ WRONG:
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Mutating the array directly!
setItems(items); // React won't detect the change
โœ… CORRECT:
const [items, setItems] = useState([1, 2, 3]);
setItems([...items, 4]); // Create a new array

๐Ÿ’ก Why? React compares old and new state by reference. If you mutate the original, the reference stays the same, so React thinks nothing changed!

Mistake 3: Using Hooks Inside Conditions

โŒ WRONG:
function MyComponent() {
  if (condition) {
    const [count, setCount] = useState(0); // Hook inside condition!
  }
}
โœ… CORRECT:
function MyComponent() {
  const [count, setCount] = useState(0); // Hook at top level
  
  if (condition) {
    // Use the state here
  }
}

Mistake 4: Stale Closure in useEffect

โŒ PROBLEM:
const [count, setCount] = useState(0);

useEffect(() => {
  const timer = setInterval(() => {
    setCount(count + 1); // Always uses count from first render!
  }, 1000);
  return () => clearInterval(timer);
}, []); // Empty array means 'count' is never updated
โœ… SOLUTION 1 - Add dependency:
useEffect(() => {
  const timer = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(timer);
}, [count]); // Re-run effect when count changes
โœ… SOLUTION 2 - Functional update:
useEffect(() => {
  const timer = setInterval(() => {
    setCount(c => c + 1); // Uses current value
  }, 1000);
  return () => clearInterval(timer);
}, []); // Can stay empty because we don't reference count

Mistake 5: Infinite Loop with useEffect

โŒ WRONG:
const [data, setData] = useState([]);

useEffect(() => {
  fetch('/api/data')
    .then(res => res.json())
    .then(setData); // Updates data
}); // No dependency array = runs every render
    // โ†’ setData causes re-render
    // โ†’ effect runs again
    // โ†’ infinite loop! ๐Ÿ’ฅ
โœ… CORRECT:
const [data, setData] = useState([]);

useEffect(() => {
  fetch('/api/data')
    .then(res => res.json())
    .then(setData);
}, []); // Empty array = run once

๐ŸŽ“ Key Takeaways

Let's solidify what you've learned:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         REACT HOOKS ESSENTIALS               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1. Hooks let functional components have      โ”‚
โ”‚    state and side effects                    โ”‚
โ”‚                                              โ”‚
โ”‚ 2. useState manages component state:         โ”‚
โ”‚    const [value, setValue] = useState(init)  โ”‚
โ”‚                                              โ”‚
โ”‚ 3. useEffect handles side effects:           โ”‚
โ”‚    useEffect(() => {...}, [dependencies])    โ”‚
โ”‚                                              โ”‚
โ”‚ 4. Dependency array controls when effects    โ”‚
โ”‚    run: [], [var], or omitted                โ”‚
โ”‚                                              โ”‚
โ”‚ 5. Return cleanup function from useEffect    โ”‚
โ”‚    to prevent memory leaks                   โ”‚
โ”‚                                              โ”‚
โ”‚ 6. Always follow the Rules of Hooks:         โ”‚
โ”‚    - Only at top level                       โ”‚
โ”‚    - Only in React functions                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿง  Mental Models to Remember

useState = Memory: Think of it as giving your component a memory bank. Each call to useState creates a slot in that memory.

useEffect = Synchronization: Think of it as keeping your component synchronized with the outside world (APIs, timers, DOM).

Dependency Array = Trigger: It's like a trigger that says "run this effect when these values change."

๐Ÿ”ง Practical Tips for Success

  1. Start with useState: Master it before diving into more complex Hooks
  2. Use ESLint: Install eslint-plugin-react-hooks to catch Hook mistakes automatically
  3. Name custom Hooks with 'use': If you create your own Hooks, start the name with "use" (e.g., useForm, useAuth)
  4. One Hook per concern: Don't try to do everything in one useState or useEffect
  5. Think in dependencies: Ask "what values does this effect depend on?" to determine the dependency array

๐Ÿ“Š Quick Reference Card

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            REACT HOOKS CHEAT SHEET             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ useState                                        โ”‚
โ”‚ const [state, setState] = useState(initial)     โ”‚
โ”‚ โ€ข Returns current state and updater function    โ”‚
โ”‚ โ€ข setState(newValue) updates and re-renders     โ”‚
โ”‚ โ€ข setState(prev => prev + 1) functional update  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ useEffect                                       โ”‚
โ”‚ useEffect(() => { /* effect */ }, [deps])       โ”‚
โ”‚ โ€ข Runs after render                             โ”‚
โ”‚ โ€ข [deps]: run when deps change                  โ”‚
โ”‚ โ€ข []: run once on mount                         โ”‚
โ”‚ โ€ข no array: run every render                    โ”‚
โ”‚ โ€ข return () => {}: cleanup function             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Rules                                           โ”‚
โ”‚ 1. Call Hooks at top level only                 โ”‚
โ”‚ 2. Call Hooks from React functions only         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Common Patterns                                 โ”‚
โ”‚ โ€ข Fetching: useEffect with empty []             โ”‚
โ”‚ โ€ข Timers: useEffect with cleanup                โ”‚
โ”‚ โ€ข Derived state: Calculate from existing state  โ”‚
โ”‚ โ€ข Form inputs: useState for each field          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŒ Real-World Applications

E-commerce: useState for shopping cart items, useEffect to persist cart to localStorage

Social Media: useState for post likes, useEffect to fetch new posts every 30 seconds

Dashboard: useState for filters, useEffect to fetch data when filters change

Forms: useState for each input field, useEffect for validation

๐ŸŽฏ What's Next?

You've mastered the foundation! Here's what to explore next:

  1. useContext: Share data without passing props
  2. useReducer: Manage complex state logic
  3. useRef: Access DOM elements and persist values
  4. useMemo & useCallback: Optimize performance
  5. Custom Hooks: Create your own reusable Hooks

๐Ÿ“š Further Study

  1. Official React Hooks Documentation: https://react.dev/reference/react - Comprehensive guide straight from the React team with interactive examples

  2. Kent C. Dodds' Epic React: https://epicreact.dev/articles - In-depth articles about Hook patterns and best practices from a React expert

  3. React Hooks Visualized: https://www.youtube.com/watch?v=1jWS7cCuUXw - Visual explanations of how Hooks work under the hood

Congratulations! ๐ŸŽ‰ You now understand the fundamentals of React Hooks. The key to mastery is practice, so start building components using useState and useEffect. Happy coding! ๐Ÿ’ปโœจ