Back to all posts
Understanding React Hooks: A Comprehensive Guide

Understanding React Hooks: A Comprehensive Guide

Understanding React Hooks: A Comprehensive Guide

React Hooks have revolutionized the way we build React components since their introduction in React 16.8. They allow you to use state and other React features without writing a class component.

Why Hooks?

Before Hooks, React developers faced several challenges:

  1. Complex components became unwieldy with lifecycle methods filled with unrelated logic
  2. Code reuse was difficult with higher-order components or render props
  3. Classes confused both people and machines, requiring understanding of 'this' binding

Hooks address these issues by allowing you to:

  • Extract stateful logic from components for reuse
  • Split components by concern rather than lifecycle methods
  • Use React features without classes

Basic Hooks

useState

The useState hook lets you add state to functional components.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect

The useEffect hook handles side effects in your component, replacing lifecycle methods like componentDidMount and componentDidUpdate.

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

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    
    // Cleanup function
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this runs only on mount/unmount
  
  return <p>Window width: {width}px</p>;
}

useContext

The useContext hook accepts a context object and returns the current context value.

import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  
  return <button className={theme}>Themed Button</button>;
}

Additional Hooks

useReducer

For complex state logic, useReducer provides an alternative to useState.

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

useCallback

The useCallback hook returns a memoized callback function, only changing if dependencies change.

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

function ParentComponent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // No dependencies, never recreated
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

useMemo

The useMemo hook memoizes expensive computations, only recalculating when dependencies change.

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

function ExpensiveCalculation({ numbers }) {
  const [dark, setDark] = useState(false);
  
  const theme = {
    backgroundColor: dark ? '#333' : '#FFF',
    color: dark ? '#FFF' : '#333'
  };
  
  // This calculation only runs when numbers change
  const sum = useMemo(() => {
    console.log('Calculating...');
    return numbers.reduce((acc, curr) => acc + curr, 0);
  }, [numbers]);
  
  return (
    <div style={theme}>
      <p>Sum: {sum}</p>
      <button onClick={() => setDark(!dark)}>Toggle theme</button>
    </div>
  );
}

Custom Hooks

One of the most powerful features of Hooks is the ability to create your own custom hooks to extract and reuse logic.

import { useState, useEffect } from 'react';

// Custom hook for form fields
function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);
  
  function handleChange(e) {
    setValue(e.target.value);
  }
  
  return {
    value,
    onChange: handleChange
  };
}

// Using the custom hook
function SignupForm() {
  const name = useFormInput('');
  const email = useFormInput('');
  
  function handleSubmit(e) {
    e.preventDefault();
    console.log('Submitting:', name.value, email.value);
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Name" {...name} />
      <input placeholder="Email" {...email} />
      <button type="submit">Sign Up</button>
    </form>
  );
}

Rules of Hooks

To ensure hooks work correctly, follow these two 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

Conclusion

React Hooks provide a powerful, elegant way to use React's features. They simplify components, make code more reusable, and allow for better organization of logic.

By mastering hooks, you can write more maintainable React code that's easier to understand and test.


This post is part of our React fundamentals series.