What are Hooks?

Hooks are functions that let you use state and other React features in functional components.

Introduced in React 16.8, Hooks revolutionized React development by allowing you to use state and lifecycle features without writing class components.

Rules of Hooks

Important Rules
  1. Only call Hooks at the top level - Don't call Hooks inside loops, conditions, or nested functions
  2. Only call Hooks from React functions - Call Hooks from React functional components or custom Hooks

Built-in Hooks Overview

Hook Purpose
useState Add state to functional components
useEffect Perform side effects (data fetching, subscriptions)
useContext Access React Context without nesting
useReducer Manage complex state logic
useRef Access DOM elements or persist values
useMemo Memoize expensive calculations
useCallback Memoize callback functions

useState Hook

The most commonly used Hook for adding state to functional components.

Basic Usage

import { useState } from 'react';

function Counter() {
  // Declare state variable
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Multiple State Variables

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [rememberMe, setRememberMe] = useState(false);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ email, password, rememberMe });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <label>
        <input
          type="checkbox"
          checked={rememberMe}
          onChange={(e) => setRememberMe(e.target.checked)}
        />
        Remember me
      </label>
      <button type="submit">Login</button>
    </form>
  );
}

Functional Updates

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

  // Good: Using functional update
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  // Multiple updates work correctly
  const incrementThreeTimes = () => {
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={incrementThreeTimes}>+3</button>
    </div>
  );
}

Lazy Initial State

function ExpensiveComponent() {
  // Function runs only once on mount
  const [state, setState] = useState(() => {
    const initialState = someExpensiveComputation();
    return initialState;
  });

  return <div>{state}</div>;
}

useEffect Hook

Perform side effects in functional components (data fetching, subscriptions, timers).

Basic useEffect

import { useState, useEffect } from 'react';

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

  // Runs after every render
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

Effect with Dependencies

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  // Runs only when userId changes
  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    }

    fetchUser();
  }, [userId]); // Dependency array

  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

Effect with Cleanup

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

  useEffect(() => {
    // Setup
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // Cleanup function
    return () => {
      clearInterval(interval);
    };
  }, []); // Empty array = run once on mount

  return <div>Seconds: {seconds}</div>;
}

Multiple Effects

function Component() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  // Effect for document title
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  // Effect for localStorage
  useEffect(() => {
    localStorage.setItem('name', name);
  }, [name]);

  // Effect for API call
  useEffect(() => {
    fetchData();
  }, []);

  return <div>...</div>;
}

useContext Hook

Access React Context without nesting Consumer components.

Creating and Using Context

import { createContext, useContext, useState } from 'react';

// Create Context
const ThemeContext = createContext();

// Provider Component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Consumer Component
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff'
      }}
    >
      Toggle Theme
    </button>
  );
}

// App
function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

Multiple Contexts

const UserContext = createContext();
const ThemeContext = createContext();

function Component() {
  const user = useContext(UserContext);
  const theme = useContext(ThemeContext);

  return (
    <div style={{ color: theme.color }}>
      Hello, {user.name}!
    </div>
  );
}

useReducer Hook

Alternative to useState for managing complex state logic.

Basic useReducer

import { useReducer } from 'react';

// Reducer function
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

Complex State Management

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);

  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', text });
  };

  return <div>{/* Todo UI */}</div>;
}

useRef Hook

Access DOM elements directly or persist values across renders.

Accessing DOM Elements

import { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Storing Mutable Values

function Timer() {
  const intervalRef = useRef(null);
  const [seconds, setSeconds] = useState(0);

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <p>Seconds: {seconds}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

Custom Hooks

Extract reusable logic into custom Hooks for better code organization.

useFetch Hook

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserList() {
  const { data, loading, error } = useFetch('/api/users');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

useLocalStorage Hook

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Usage
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current theme: {theme}
    </button>
  );
}

useToggle Hook

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = () => setValue(v => !v);
  const setTrue = () => setValue(true);
  const setFalse = () => setValue(false);

  return [value, { toggle, setTrue, setFalse }];
}

// Usage
function Modal() {
  const [isOpen, { toggle, setFalse }] = useToggle();

  return (
    <div>
      <button onClick={toggle}>Open Modal</button>
      {isOpen && (
        <div className="modal">
          <button onClick={setFalse}>Close</button>
        </div>
      )}
    </div>
  );
}