Tutorial
May 30, 2026
18 min read

React Hooks Complete Guide 2026: Master Modern React Development

Everything you need to know about React Hooks - from basics to advanced patterns. Learn useState, useEffect, useContext, and create your own custom hooks.

Rehman Farouq

Full Stack Developer | React Expert

Introduction to React Hooks

React Hooks revolutionized how we write React components. Introduced in React 16.8, hooks allow you to use state and other React features in functional components, making your code more readable, reusable, and maintainable.

In this comprehensive guide, we'll explore all the built-in hooks and learn how to create custom hooks for your specific needs.

useState: Managing Component State

The useState hook is the most fundamental hook in React. It allows you to add state to functional components.

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Guest');

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
    </div>
  );
};

export default Counter;

Key useState Patterns

  • Functional updates: setCount(prev => prev + 1)
  • Lazy initialization: useState(() => computeExpensiveValue())
  • Object state: Use spread operator for updates

useEffect: Handling Side Effects

The useEffect hook lets you perform side effects in functional components. It's equivalent to componentDidMount, componentDidUpdate, and componentWillUnmount combined.

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

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch user data
    fetchUser(userId)
      .then(userData => {
        setUser(userData);
        setLoading(false);
      });

    // Cleanup function
    return () => {
      // Cleanup code here
    };
  }, [userId]); // Dependency array

  if (loading) return <div>Loading...</div>;
  return <div>{user.name}</div>;
};

Use Effect Patterns

  • Run once: Empty dependency array []
  • Run on specific changes: [prop, state]
  • Cleanup: Return function from useEffect

useContext: Sharing Data Across Components

useContext allows you to share data between components without prop drilling. It's perfect for theme data, user authentication, and global state.

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

// Create context
const ThemeContext = createContext();

// Provider component
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// Custom hook for using theme
const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
};

// Usage in component
const Header = () => {
  const { theme, setTheme } = useTheme();
  
  return (
    <header className={theme}>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </header>
  );
};

useReducer: Complex State Management

useReducer is an alternative to useState for complex state logic. It's especially useful when state depends on previous values or when you have multiple sub-values.

import React, { useReducer } from 'react';

// Reducer function
const 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;
  }
};

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

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

Performance Optimization: useCallback & useMemo

These hooks help optimize performance by memoizing functions and values to prevent unnecessary re-renders.

import React, { useState, useCallback, useMemo } from 'react';

const ExpensiveComponent = ({ items }) => {
  const [filter, setFilter] = useState('');

  // Memoize expensive calculation
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => 
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // Memoize event handler
  const handleItemClick = useCallback((item) => {
    console.log('Item clicked:', item);
  }, []);

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items..."
      />
      {filteredItems.map(item => (
        <div key={item.id} onClick={() => handleItemClick(item)}>
          {item.name}
        </div>
      ))}
    </div>
  );
};

When to Use Each

useCallback

  • • Event handlers passed to child components
  • • Functions used in useEffect dependencies
  • • Preventing unnecessary re-renders

useMemo

  • • Expensive calculations
  • • Derived state
  • • Reference equality checks

Creating Custom Hooks

Custom hooks are JavaScript functions whose names start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.

// Custom hook for API calls
const useApi = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

    fetchData();
  }, [url]);

  return { data, loading, error };
};

// Usage in component
const UserProfile = ({ userId }) => {
  const { data: user, loading, error } = useApi(`/api/users/${userId}`);

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

  return <div>{user.name}</div>;
};

Rules of Hooks

Two Essential Rules

  1. 1.
    Only Call Hooks at the Top Level:

    Don't call hooks inside loops, conditions, or nested functions.

  2. 2.
    Only Call Hooks from React Functions:

    Call hooks from React functional components or custom hooks.

Conclusion

React Hooks have transformed how we write React applications. They provide a more direct API to React concepts and make code reuse and organization much easier.

By mastering these hooks and patterns, you'll be able to build more efficient, maintainable, and scalable React applications. Remember to always follow the Rules of Hooks and use TypeScript when possible for better development experience.

Keep practicing with these patterns, and soon you'll be writing clean, efficient React code like a pro!

Ready to Master React?

Check out my other React tutorials and start building amazing applications!