From Classes to Functions
In older React code, components were written as JavaScript classes. While still supported, the modern and recommended approach is to use Functional Components. They are simpler, require less boilerplate code, and are easier to read and test.
A functional component is just a JavaScript function that accepts props and returns JSX. But how do we add state or tap into the component lifecycle without a class? The answer is Hooks.
What are Hooks?
Hooks are special functions, always starting with use, that let you "hook into" React features from within functional components. They let you use state, lifecycle methods, and other React features without writing a class.
The Rules of Hooks:
- Only call Hooks at the top level. Don't call them inside loops, conditions, or nested functions.
- Only call Hooks from React function components or from your own custom Hooks.
useState: The State Hook
The useState Hook is how you add state to a functional component. It's the most common Hook you'll use.
When you call useState, you pass it the initial value of your state. It returns an array containing two things:
- The current state value.
- A function to update that value.
We almost always use array destructuring to get these two values.
JavaScript
import React, { useState } from 'react';
function Counter() {
  // Call useState with the initial state (0)
  // It returns the current state (`count`) and a function to update it (`setCount`)
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={increment}>Click me</button>
    </div>
  );
}
When setCount is called, React re-renders the Counter component, and the count variable will have the new value.
useEffect: The Effect Hook
What if you need to do something after React has rendered your component to the DOM? This could be fetching data, setting up a subscription, or manually changing the DOM. These operations are called side effects, and the useEffect Hook is how you handle them.
useEffect takes two arguments:
- A setup function that contains your side effect code.
- An optional dependency array.
The dependency array is crucial. It tells React when to re-run your effect.
- No dependency array: The effect runs after every render. (Use with caution!)
- Empty array []: The effect runs only once, after the initial render (the "mounting" phase). This is perfect for initial data fetching.
- Array with values [prop, state]: The effect runs once initially, and then again only if any of the values in the array have changed since the last render.
JavaScript
import React, { useState, useEffect } from 'react';
function UserData({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    console.log("Effect is running!");
    setLoading(true);
    
    // 1. The setup function (the effect)
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
      
    // The "cleanup" function (optional)
    return () => {
      console.log("Cleaning up previous effect.");
      // This is where you would cancel a subscription or network request.
    };
  }, [userId]); // 2. The dependency array
  if (loading) {
    return <p>Loading...</p>;
  }
  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
}
In this example, the data fetching effect will run when the component first mounts, and it will run again only if the userId prop changes.