React Hooks

March 9, 2025 (3w ago)

React Hooks are a powerful feature introduced in React 16.8 that allow you to use state and other React features in functional components without writing class components. They make your code cleaner, more reusable, and easier to understand. In this blog, we’ll go through all the built-in React Hooks, explain them in simple words, provide easy code examples, and also dive into creating custom hooks.


1. useState - Manage State in Functional Components

The useState hook lets you add state to your functional components. It gives you a state variable and a function to update it.

Syntax

const [state, setState] = useState(initialValue);

Example

Let’s create a simple counter app.

import React, { useState } from "react";
 
function Counter() {
  const [count, setCount] = useState(0); // Initial state is 0
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
 
export default Counter;

Explanation

  • count is the state variable.
  • setCount is the function to update the state.
  • When you click the button, setCount updates the count, and the component re-renders with the new value.

2. useEffect - Handle Side Effects

The useEffect hook lets you perform side effects in your components, like fetching data, updating the DOM, or setting up subscriptions. It’s like the combination of componentDidMount, componentDidUpdate, and componentWillUnmount in class components.

Syntax

useEffect(() => {
  // Side effect code here
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]);

Example

Let’s fetch some data when the component mounts.

import React, { useState, useEffect } from "react";
 
function DataFetcher() {
  const [data, setData] = useState(null);
 
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts/1")
      .then((response) => response.json())
      .then((data) => setData(data));
  }, []); // Empty array means this runs only once when the component mounts
 
  return <div>{data ? <p>{data.title}</p> : <p>Loading...</p>}</div>;
}
 
export default DataFetcher;

Explanation

  • useEffect runs the fetch request when the component mounts (because of the empty [] dependency array).
  • The data is stored in the data state using setData.
  • If the dependency array had a value like [count], the effect would run every time count changes.

3. useContext - Access Context Easily

The useContext hook lets you access the React Context API without wrapping your component in a Consumer.

Syntax

const value = useContext(MyContext);

Example

Let’s create a simple theme switcher using context.

import React, { useContext, createContext } from "react";
 
// Create a context
const ThemeContext = createContext();
 
function ThemeComponent() {
  const theme = useContext(ThemeContext); // Access the context value
 
  return <p>The current theme is {theme}</p>;
}
 
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemeComponent />
    </ThemeContext.Provider>
  );
}
 
export default App;

Explanation

  • We create a ThemeContext using createContext().
  • The App component provides the theme value ("dark") using ThemeContext.Provider.
  • The ThemeComponent uses useContext to access the theme value and display it.

4. useReducer - Manage Complex State Logic

The useReducer hook is an alternative to useState when you have complex state logic or multiple state transitions.

Syntax

const [state, dispatch] = useReducer(reducer, initialState);

Example

Let’s create a counter with increment, decrement, and reset actions.

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 };
    case "reset":
      return { count: 0 };
    default:
      return state;
  }
}
 
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
 
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>Increment</button>
      <button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  );
}
 
export default Counter;

Explanation

  • reducer is a function that defines how the state changes based on the action.
  • dispatch is used to send actions to the reducer.
  • This is useful for managing more complex state logic compared to useState.

5. useCallback - Memoize Functions

The useCallback hook returns a memoized version of a callback function that only changes if one of its dependencies changes. This is useful for optimizing performance when passing callbacks to child components.

Syntax

const memoizedCallback = useCallback(() => {
  // Your callback logic
}, [dependencies]);

Example

import React, { useState, useCallback } from "react";
 
function Parent() {
  const [count, setCount] = useState(0);
 
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);
 
  return (
    <div>
      <p>Count: {count}</p>
      <Child onIncrement={increment} />
    </div>
  );
}
 
function Child({ onIncrement }) {
  return <button onClick={onIncrement}>Increment</button>;
}
 
export default Parent;

Explanation

  • useCallback ensures the increment function doesn’t get recreated on every render unless count changes.
  • This prevents unnecessary re-renders of the Child component.

6. useMemo - Memoize Expensive Computations

The useMemo hook memoizes the result of a computation so it’s only recalculated when its dependencies change. This is great for optimizing performance.

Syntax

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Example

import React, { useState, useMemo } from "react";
 
function ExpensiveCalculation() {
  const [count, setCount] = useState(0);
 
  const expensiveValue = useMemo(() => {
    console.log("Calculating...");
    return count * 2;
  }, [count]);
 
  return (
    <div>
      <p>Expensive Value: {expensiveValue}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
 
export default ExpensiveCalculation;

Explanation

  • The expensiveValue is only recalculated when count changes.
  • Without useMemo, the computation would run on every render, even if count doesn’t change.

7. useRef - Reference DOM Elements or Persist Values

The useRef hook lets you create a mutable reference that persists across renders. It’s often used to access DOM elements or store values that don’t trigger re-renders when updated.

Syntax

const ref = useRef(initialValue);

Example

Let’s create an input field and focus it on button click.

import React, { useRef } from "react";
 
function FocusInput() {
  const inputRef = useRef(null);
 
  const handleClick = () => {
    inputRef.current.focus();
  };
 
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}
 
export default FocusInput;

Explanation

  • inputRef holds a reference to the input element.
  • inputRef.current gives you direct access to the DOM element, so you can call methods like focus().

8. useLayoutEffect - Like useEffect, but Runs Synchronously

The useLayoutEffect hook is similar to useEffect, but it runs synchronously after all DOM mutations. Use it when you need to measure or update the DOM before the browser paints.

Syntax

useLayoutEffect(() => {
  // Your code
}, [dependencies]);

Example

import React, { useLayoutEffect, useRef } from "react";
 
function LayoutEffectExample() {
  const divRef = useRef(null);
 
  useLayoutEffect(() => {
    console.log("Div width:", divRef.current.offsetWidth);
  }, []);
 
  return <div ref={divRef} style={{ width: "200px", height: "200px", background: "lightblue" }} />;
}
 
export default LayoutEffectExample;

Explanation

  • useLayoutEffect runs after the DOM updates but before the browser paints, so it’s ideal for measuring DOM elements.
  • Use useEffect for most side effects unless you specifically need synchronous updates.

9. useDebugValue - Debug Custom Hooks

The useDebugValue hook is used to display a label for custom hooks in React DevTools, making debugging easier.

Syntax

useDebugValue(value);

Explanation

This is typically used inside custom hooks. We’ll see it in action when we create a custom hook below.


What Are Custom Hooks?

Custom Hooks are JavaScript functions that start with use and allow you to reuse stateful logic across multiple components. They let you extract logic from components and share it without repeating code.


Creating a Custom Hook

Let’s create a custom hook to manage form input state.

Example: useFormInput

import { useState } from "react";
 
function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);
 
  const handleChange = (e) => {
    setValue(e.target.value);
  };
 
  return {
    value,
    onChange: handleChange,
  };
}
 
function Form() {
  const nameInput = useFormInput(""); // Custom hook for name
  const emailInput = useFormInput(""); // Custom hook for email
 
  return (
    <div>
      <input placeholder="Name" {...nameInput} />
      <input placeholder="Email" {...emailInput} />
      <p>Name: {nameInput.value}</p>
      <p>Email: {emailInput.value}</p>
    </div>
  );
}
 
export default Form;

Explanation

  • useFormInput is a custom hook that manages the state of an input field.
  • It returns the current value and an onChange handler.
  • We use it twice in the Form component to manage two different inputs (name and email).
  • This avoids duplicating the same state logic in multiple places.

Adding useDebugValue to a Custom Hook

Let’s add useDebugValue to our custom hook for better debugging.

import { useState, useDebugValue } from "react";
 
function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);
 
  const handleChange = (e) => {
    setValue(e.target.value);
  };
 
  // Add debug value for React DevTools
  useDebugValue(value ? `Input: ${value}` : "Empty");
 
  return {
    value,
    onChange: handleChange,
  };
}
 
export default useFormInput;

Explanation

  • useDebugValue adds a label to the custom hook in React DevTools.
  • When you inspect the useFormInput hook in DevTools, it will show the current input value (or "Empty" if the input is empty).

Conclusion

React Hooks make functional components more powerful and reusable. Here’s a quick recap of what we covered:

  1. useState: Manage state in functional components.
  2. useEffect: Handle side effects like fetching data.
  3. useContext: Access context values easily.
  4. useReducer: Manage complex state logic.
  5. useCallback: Memoize functions for performance.
  6. useMemo: Memoize expensive computations.
  7. useRef: Reference DOM elements or persist values.
  8. useLayoutEffect: Like useEffect, but synchronous.
  9. useDebugValue: Debug custom hooks in DevTools.

We also learned how to create custom hooks to reuse logic across components, making our code DRY (Don’t Repeat Yourself).

Start using Hooks in your projects—they’ll make your React code cleaner and more maintainable! If you have any questions or want more examples, let me know in the comments. Happy coding! 🚀