React Hooks revolutionized how we write React components when they were introduced in React 16.8. They allow functional components to manage state, handle side effects, and access React features that were previously only available in class components. This comprehensive guide covers all essential Hooks, their use cases, best practices, and advanced patterns.
The Evolution: From Classes to Hooks
Before Hooks, React developers had to use class components for stateful logic and lifecycle methods. Hooks brought these capabilities to functional components, making React code more reusable, easier to test, and more intuitive. Understanding Hooks is essential for modern React development.
useState Hook: Managing Component State
The useState Hook is the most fundamental Hook for managing component state. It's the functional component equivalent of this.state and this.setState in class components.
Basic useState Usage
useState returns an array with two elements: the current state value and a function to update it.
Functional Updates
When the new state depends on the previous state, use the functional update form to avoid stale closures:
Lazy Initial State
If the initial state is expensive to compute, pass a function to useState. It will only run once:
Multiple State Variables
You can use multiple useState calls in a single component. React will preserve state between re-renders.
useEffect Hook: Side Effects and Lifecycle
The useEffect Hook handles side effects like data fetching, subscriptions, or DOM manipulation. It combines the functionality of componentDidMount, componentDidUpdate, and componentWillUnmount.
Basic useEffect Pattern
useEffect runs after every render by default. You can control when it runs using the dependency array.
Dependency Array
The dependency array controls when the effect runs:
- No array: Runs after every render
- Empty array
[]: Runs only on mount - Array with values: Runs when those values change
Cleanup Function
Return a cleanup function from useEffect to clean up subscriptions, timers, or event listeners:
Common useEffect Patterns
useEffect is commonly used for:
- Data fetching
- Setting up subscriptions
- DOM manipulation
- Setting up timers
- Logging and analytics
useContext Hook: Accessing Context
The useContext Hook accesses React Context without prop drilling, making it easier to share state across components.
Using useContext
useContext takes a context object and returns the current context value:
When to Use useContext
Use Context API and useContext for:
- Theme preferences
- User authentication state
- Language/localization
- Shared UI state
useReducer Hook: Complex State Logic
The useReducer Hook is an alternative to useState, ideal for complex state logic or when the next state depends on the previous one.
useReducer Pattern
useReducer follows the Redux pattern with actions and reducers:
When to Use useReducer
Consider useReducer when:
- State logic is complex
- Multiple sub-values in state
- Next state depends on previous state
- You want predictable state updates
useMemo Hook: Memoizing Expensive Calculations
The useMemo Hook memoizes expensive calculations, only recomputing when dependencies change.
useMemo Usage
useMemo takes a function and dependency array, returning the memoized value:
When to Use useMemo
Use useMemo for:
- Expensive calculations
- Derived state that's costly to compute
- Preventing unnecessary recalculations
Performance Considerations
Don't overuse useMemo. It has its own overhead, so only use it when the computation is genuinely expensive.
useCallback Hook: Memoizing Functions
The useCallback Hook memoizes functions, returning the same function reference when dependencies haven't changed.
useCallback Usage
useCallback is useful when passing functions as props to memoized components:
When to Use useCallback
Use useCallback when:
- Passing functions to memoized child components
- Functions are dependencies in other Hooks
- Creating stable function references
useRef Hook: Mutable References
The useRef Hook creates a mutable reference that persists across renders without causing re-renders.
Accessing DOM Elements
useRef is commonly used to access DOM elements:
Storing Mutable Values
useRef can store any mutable value that doesn't need to trigger re-renders:
Custom Hooks: Reusable Logic
Custom Hooks allow you to extract component logic into reusable functions. They're the key to sharing stateful logic between components.
Creating Custom Hooks
Custom Hooks are JavaScript functions that start with "use" and can call other Hooks:
Common Custom Hook Patterns
Popular custom Hook patterns include:
- Data fetching hooks
- Local storage hooks
- Form handling hooks
- Window size hooks
- Debounce/throttle hooks
Sharing Custom Hooks
Custom Hooks can be shared across components, projects, or published as npm packages.
Additional Built-in Hooks
React provides several other Hooks for specific use cases:
useLayoutEffect
Similar to useEffect but runs synchronously after all DOM mutations. Use for DOM measurements or animations.
useImperativeHandle
Customizes the instance value exposed when using ref with forwardRef. Rarely needed.
useDebugValue
Adds a label to custom Hooks in React DevTools. Useful for debugging.
Rules of Hooks
Hooks have strict rules that must be followed. Violating these rules can lead to bugs.
Rule 1: Only Call Hooks at the Top Level
Don't call Hooks inside loops, conditions, or nested functions. Always call them at the top level of your component.
Rule 2: Only Call Hooks from React Functions
Call Hooks from:
- React function components
- Custom Hooks
Don't call them from:
- Regular JavaScript functions
- Class components
- Event handlers (directly)
Why These Rules Exist
React relies on the order of Hook calls to preserve state between renders. Violating the rules breaks this assumption.
Advanced Hook Patterns
Composing Multiple Hooks
Combine multiple Hooks to build complex functionality:
Conditional Hooks (Anti-pattern)
Never call Hooks conditionally. If you need conditional logic, put it inside the Hook or use the Hook's return value conditionally.
Hook Dependencies
Understanding dependencies is crucial. Use ESLint's exhaustive-deps rule to catch missing dependencies.
Performance Optimization with Hooks
Hooks provide several ways to optimize performance:
- useMemo for expensive calculations
- useCallback for stable function references
- React.memo with useCallback for component memoization
- Proper dependency arrays to prevent unnecessary effects
Common Mistakes and How to Avoid Them
Common Hook mistakes include:
- Missing dependencies in useEffect
- Creating functions in render without useCallback
- Overusing useMemo and useCallback
- Not cleaning up effects
- Calling Hooks conditionally
Testing Hooks
Test Hooks using React Testing Library or by testing components that use them. For custom Hooks, use renderHook from React Testing Library.
Conclusion
Hooks make React code more reusable, easier to test, and more intuitive. Mastering Hooks is essential for modern React development. Start with useState and useEffect, then gradually learn other Hooks as you need them. Remember the rules of Hooks, understand dependencies, and use performance optimization Hooks wisely. With practice, Hooks will become second nature, and you'll write cleaner, more maintainable React code.