Skip to main content

React Hooks Tutorial: Complete Guide from Basics to Advanced (2025)

November 2, 2025 15 min read
49
react-hooks-tutorial-complete-guide

React Hooks Tutorial: Complete Guide from Basics to Advanced (2025)

React Hooks revolutionized how we write React components when they were introduced in React 16.8. If you're still writing class components or struggling to understand hooks, this comprehensive guide will transform you from a beginner to a React Hooks expert with practical examples, best practices, and real-world use cases you can implement today.

📚 Related Learning Resources:

What are React Hooks?

React Hooks are special functions that let you "hook into" React features like state and lifecycle methods from functional components. Before hooks, you needed class components to use state and lifecycle methods. Now, you can do everything with functional components, making your code cleaner, more reusable, and easier to test.

Think of hooks as superpowers for your functional components. They allow you to:

  • Add state to functional components without classes
  • Share stateful logic between components easily
  • Split complex components into smaller functions
  • Use React features without learning complex class syntax
  • Write more readable and maintainable code

Why React Hooks are Important: Benefits and Advantages

Developer Experience Benefits:

  • Simpler Code: No more confusing 'this' keyword and binding methods
  • Better Code Reuse: Share logic between components with custom hooks
  • Easier Testing: Pure functions are simpler to test than classes
  • Modern Standard: 90% of new React code uses hooks
  • Better Performance: Functional components with hooks are faster than classes

How React Hooks Work: Core Concepts

The Rules of Hooks:

  1. Only Call Hooks at the Top Level: Never call hooks inside loops, conditions, or nested functions
  2. Only Call Hooks from React Functions: Call hooks from functional components or custom hooks
JAVASCRIPT
// ❌ WRONG - Hook inside condition
function MyComponent() {
  if (someCondition) {
    const [state, setState] = useState(0); // Error!
  }
}

// ✅ CORRECT - Hook at top level
function MyComponent() {
  const [state, setState] = useState(0);
  
  if (someCondition) {
    // Use state here
  }
}

useState Hook: Managing Component State

The useState hook is the most fundamental hook. It adds state to functional components.

Basic Usage:

REACT COMPONENT
import React, { useState } from 'react';

function Counter() {
  // Declare state variable
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Multiple State Variables:

REACT FORM
function UserForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ name, email, age });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input 
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

💡 Pro Tip: Looking to enhance your development workflow? Check out our guide on AI Automation Tools in 2025 to 10x Your Productivity

useEffect Hook: Handling Side Effects

The useEffect hook lets you perform side effects in functional components. It replaces componentDidMount, componentDidUpdate, and componentWillUnmount.

Basic useEffect:

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

function DocumentTitle() {
  const [count, setCount] = useState(0);
  
  // Runs after every render
  useEffect(() => {
    document.title = `Count: ${count}`;
  });
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

useEffect with Dependency Array:

DATA FETCHING
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // Only runs when userId changes
  useEffect(() => {
    setLoading(true);
    
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]); // Dependency array
  
  if (loading) return <p>Loading...</p>;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Cleanup Function:

TIMER WITH CLEANUP
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    
    // Cleanup function
    return () => {
      clearInterval(interval);
    };
  }, []); // Empty array = runs once
  
  return <p>Seconds: {seconds}</p>;
}

useContext Hook: Sharing Data Across Components

The useContext hook provides a way to share data across the component tree without prop drilling.

CONTEXT EXAMPLE
import React, { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext();

// Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === '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}>
      Toggle Theme (Current: {theme})
    </button>
  );
}

useReducer Hook: Managing Complex State Logic

The useReducer hook is an alternative to useState for managing complex state logic. It's similar to Redux reducers.

USEREDUCER EXAMPLE
import React, { 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>
  );
}

useCallback and useMemo: Performance Optimization

These hooks help optimize performance by preventing unnecessary re-renders and recalculations.

useCallback - Memoize Functions:

USECALLBACK EXAMPLE
import React, { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(0);
  
  // Function is only recreated when dependencies change
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []); // Empty deps = never recreated
  
  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent onIncrement={increment} />
      <button onClick={() => setOther(other + 1)}>
        Other: {other}
      </button>
    </div>
  );
}

useMemo - Memoize Expensive Calculations:

USEMEMO EXAMPLE
import React, { useState, useMemo } from 'react';

function ExpensiveComponent({ items }) {
  const [filter, setFilter] = useState('');
  
  // Expensive calculation only runs when dependencies change
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => 
      item.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);
  
  return (
    <div>
      <input 
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items"
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

useRef Hook: Accessing DOM and Persisting Values

USEREF EXAMPLE
import React, { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    // Focus input on mount
    inputRef.current.focus();
  }, []);
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={() => inputRef.current.focus()}>
        Focus Input
      </button>
    </div>
  );
}

Custom Hooks: Creating Reusable Logic

Custom hooks let you extract component logic into reusable functions. They're one of the most powerful features of React Hooks.

useLocalStorage Hook:

CUSTOM HOOK - LOCALSTORAGE
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', 'Guest');
  
  return (
    <input 
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  );
}

useFetch Hook:

CUSTOM HOOK - DATA FETCHING
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    setLoading(true);
    
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading, error };
}

// Usage
function UserList() {
  const { data, loading, error } = useFetch('https://api.example.com/users');
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

🤖 AI & Development: As AI transforms development, understanding fundamental concepts like React Hooks becomes even more important. Learn about AI Rules in 2025: What You Need to Know Right Now

Real-World React Hooks Project: Authentication System

COMPLETE AUTH SYSTEM
// AuthContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Check if user is logged in on mount
    const token = localStorage.getItem('token');
    if (token) {
      verifyToken(token)
        .then(userData => setUser(userData))
        .catch(() => localStorage.removeItem('token'))
        .finally(() => setLoading(false));
    } else {
      setLoading(false);
    }
  }, []);
  
  const login = async (email, password) => {
    const { user, token } = await loginAPI(email, password);
    localStorage.setItem('token', token);
    setUser(user);
  };
  
  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}

React Hooks vs Class Components

Before Hooks (Class Component):

CLASS COMPONENT
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.increment = this.increment.bind(this);
  }
  
  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }
  
  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }
  
  increment() {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

With Hooks (Functional Component):

FUNCTIONAL COMPONENT WITH HOOKS
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Comparison Table:

Feature Class Components Hooks
Code Length More verbose More concise
Learning Curve Complex (this, binding) Easier to learn
Code Reuse HOCs, Render Props Custom Hooks
Performance Good Better
Bundle Size Larger Smaller
Testing More complex Easier
Future Support Maintained but legacy Actively developed

Common React Hooks Mistakes and Best Practices

Common Mistakes to Avoid:

❌ Breaking Rules of Hooks: Always call hooks at the top level
WRONG VS CORRECT
// ❌ WRONG
function MyComponent({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // Error!
  }
}

// ✅ CORRECT
function MyComponent({ condition }) {
  const [state, setState] = useState(0);
  
  if (condition) {
    // Use state here
  }
}
❌ Missing Dependencies in useEffect: Always include all dependencies
DEPENDENCY ARRAY ISSUE
// ❌ WRONG
function MyComponent({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // Missing userId!
}

// ✅ CORRECT
function MyComponent({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // Include userId
}

Best Practices:

✅ Write Good Hook Patterns:
  • Name custom hooks with 'use' prefix
  • Keep hooks small and focused
  • Extract reusable logic into custom hooks
  • Always cleanup side effects
  • Use TypeScript for better type safety

Advanced Hooks: useImperativeHandle, useLayoutEffect, useDebugValue

useImperativeHandle - Expose Methods to Parent Components:

USEIMPERATIVEHANDLE
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));
  
  return <input ref={inputRef} {...props} />;
});

// Parent component
function Parent() {
  const inputRef = useRef();
  
  return (
    <div>
      <FancyInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => inputRef.current.clear()}>Clear</button>
    </div>
  );
}

useLayoutEffect - Synchronous DOM Updates:

USELAYOUTEFFECT
import React, { useState, useLayoutEffect, useRef } from 'react';

function Tooltip() {
  const [show, setShow] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const buttonRef = useRef();
  
  // Runs synchronously after DOM mutations
  useLayoutEffect(() => {
    if (show) {
      const rect = buttonRef.current.getBoundingClientRect();
      setPosition({
        x: rect.left + rect.width / 2,
        y: rect.top - 10
      });
    }
  }, [show]);
  
  return (
    <>
      <button 
        ref={buttonRef}
        onMouseEnter={() => setShow(true)}
        onMouseLeave={() => setShow(false)}
      >
        Hover me
      </button>
      {show && (
        <div 
          style={{
            position: 'absolute',
            left: position.x,
            top: position.y
          }}
        >
          Tooltip
        </div>
      )}
    </>
  );
}

Testing React Hooks

Testing hooks is crucial for maintaining reliable applications. Here's how to test hooks effectively:

TESTING HOOKS
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('should increment counter', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  it('should decrement counter', () => {
    const { result } = renderHook(() => useCounter(5));
    
    expect(result.current.count).toBe(5);
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(4);
  });
});

Hook Performance Optimization Tips

  1. Use React.memo for Component Memoization: Prevent unnecessary re-renders of child components
  2. Implement useCallback for Event Handlers: Memoize functions passed as props
  3. Apply useMemo for Expensive Calculations: Cache computed values
  4. Lazy Initial State: Use function form for expensive initial state calculations
  5. Split State Appropriately: Don't combine unrelated state variables
  6. Use useReducer for Complex State: Better than multiple useState calls
PERFORMANCE OPTIMIZATION
// Lazy initial state
const [state, setState] = useState(() => {
  // Expensive computation runs only once
  return computeExpensiveInitialState();
});

// Memoized component with React.memo
const ExpensiveChild = React.memo(({ data, onClick }) => {
  console.log('Child rendered');
  return <div onClick={onClick}>{data}</div>;
});

// Parent using useCallback
function Parent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState('Hello');
  
  // Memoized callback
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // Empty deps = stable reference
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ExpensiveChild data={data} onClick={handleClick} />
    </div>
  );
}

React 18+ Hooks: Latest Features

React 18 introduced new hooks that enhance performance and user experience:

useTransition - Mark Updates as Non-Urgent:

USETRANSITION
import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value); // Urgent update
    
    startTransition(() => {
      // Non-urgent update
      const filtered = searchLargeDataset(value);
      setResults(filtered);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending && <span>Loading...</span>}
      <ResultsList results={results} />
    </div>
  );
}

useDeferredValue - Defer Updates:

USEDEFERREDVALUE
import { useState, useDeferredValue, useMemo } from 'react';

function SearchList() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  
  const list = useMemo(() => 
    generateLargeList(deferredText),
    [deferredText]
  );
  
  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <div style={{ opacity: text !== deferredText ? 0.5 : 1 }}>
        {list}
      </div>
    </>
  );
}

FAQs About React Hooks

Q1: Can I use hooks in class components?

No, hooks only work in functional components. However, you can gradually migrate to hooks by creating new components as functional components while keeping existing class components.

Q2: Do hooks replace Redux or other state management?

Not necessarily. For simple state management, useContext and useReducer can replace Redux. However, for large applications with complex state logic, Redux still has advantages.

Q3: How do I test components with hooks?

Use React Testing Library which provides utilities like renderHook for testing custom hooks.

Q4: Why am I getting infinite loops with useEffect?

This usually happens when you forget to include a dependency array, or when you're updating state that's in the dependency array without proper checks.

Q5: Should I use one useState or multiple?

Use multiple useState calls when values are independent and change separately. Use one useState with an object when values are closely related.

Q6: What's the difference between useEffect and useLayoutEffect?

useEffect runs asynchronously after render and paint, while useLayoutEffect runs synchronously after render but before paint.

Q7: Can I create my own hooks?

Yes! Custom hooks are one of the most powerful features. Extract any logic that uses hooks into a custom hook by creating a function that starts with "use".

Conclusion

React Hooks have revolutionized how we write React applications, making code more readable, maintainable, and reusable. Throughout this comprehensive guide, we've covered everything from basic hooks like useState and useEffect to advanced patterns like custom hooks, performance optimization, and real-world applications.

The key to mastering React Hooks is practice. Start by converting simple class components to functional components, then gradually explore advanced hooks like useReducer and useContext. Create custom hooks to share logic across your application, and always follow the rules of hooks to avoid common pitfalls.

Ready to level up your React skills? Start refactoring your components with hooks today, build custom hooks for your common patterns, and explore the React ecosystem!

🚀 Next Steps in Your Learning Journey:


Additional Resources

Quick Reference: Essential React Hooks

Hook Purpose Basic Usage
useState Add state to components const [state, setState] = useState(initial)
useEffect Handle side effects useEffect(() => { }, [deps])
useContext Access context values const value = useContext(Context)
useReducer Complex state logic const [state, dispatch] = useReducer(reducer, init)
useCallback Memoize functions const fn = useCallback(() => { }, [deps])
useMemo Memoize values const value = useMemo(() => calc(), [deps])
useRef Access DOM/persist values const ref = useRef(initial)

Tags: React Hooks, React Tutorial, JavaScript, Web Development, Frontend Development, useState, useEffect, Custom Hooks, React 18, Modern React, React 2025

Last Updated: November 2025

Author: KnowledgeMarkG Team

Share this article

Kausar Raza
Founder and Lead Author at Knowledge Mark G

Kausar Raza

Passionate about sharing knowledge and insights.

Published on
November 2, 2025
15 min read
49

Comments (0)

Leave a Comment

No comments yet. Be the first to comment!