React Hooks Explained

Jane Smith·
October 20, 2023
·2 min read
React
Hooks
JavaScript
React Hooks Explained

React Hooks Explained

React Hooks were introduced in React 16.8 as a way to use state and other React features without writing a class component. They allow you to "hook into" React state and lifecycle features from function components.

Why Hooks?

Before Hooks, if you needed state in a component, you had to use a class component. This led to complex components that were difficult to understand and reuse. Hooks solve these problems by:

  • Allowing you to reuse stateful logic between components
  • Organizing related code together (instead of splitting across lifecycle methods)
  • Using functions instead of classes (which are easier to understand)

The Basic Hooks

useState

The useState hook lets you add React state to function 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 lets you perform side effects in function components. It's like componentDidMount, componentDidUpdate, and componentWillUnmount combined:

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
    
    // Cleanup function (like componentWillUnmount)
    return () => {
      document.title = 'React App';
    };
  }, [count]); // Only re-run if count changes

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

useContext

The useContext hook lets you subscribe to React context without introducing nesting:

import React, { useContext } from 'react';

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

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={`btn-${theme}`}>I am styled by theme context!</button>;
}

Additional Hooks

useReducer

useReducer is usually preferable to useState when you have complex state logic:

import React, { useReducer } from 'react';

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, { count: 0 });
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useCallback

useCallback returns a memoized callback that only changes if one of its dependencies changes:

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

function ParentComponent() {
  const [count, setCount] = useState(0);
  
  // This function only changes when count changes
  const incrementCount = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  
  return (
    <div>
      <ChildComponent onIncrement={incrementCount} />
      <p>Count: {count}</p>
    </div>
  );
}

useMemo

useMemo is similar to useCallback but for values instead of functions:

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

function ExpensiveCalculation({ a, b }) {
  // Only recalculate when a or b changes
  const result = useMemo(() => {
    console.log('Calculating...');
    return a * b;
  }, [a, b]);
  
  return <div>Result: {result}</div>;
}

Building Custom Hooks

One of the most powerful features of Hooks is the ability to create your own custom hooks:

import { useState, useEffect } from 'react';

// Custom hook to manage window size
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });
  
  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    
    window.addEventListener('resize', handleResize);
    handleResize(); // Set size initially
    
    return () => window.removeEventListener('resize', handleResize);
  }, []); // Empty array ensures effect runs only once
  
  return windowSize;
}

// Usage in a component
function ResponsiveComponent() {
  const { width, height } = useWindowSize();
  
  return (
    <div>
      Window size: {width} x {height}
    </div>
  );
}

Rules of Hooks

There are two essential rules to follow when using Hooks:

  1. Only call Hooks at the top level - Don't call Hooks inside loops, conditions, or nested functions.
  2. Only call Hooks from React function components - Don't call Hooks from regular JavaScript functions.

These rules ensure that Hooks are called in the same order each time a component renders, which is important for React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

Conclusion

React Hooks provide a more direct API to React concepts you already know: props, state, context, refs, and lifecycle. They allow you to split one component into smaller functions based on what pieces are related, rather than forcing a split based on lifecycle methods.

By using Hooks, you can write more concise and reusable code, making your React applications easier to understand and maintain.