Skip to content
Feb 28

React Hooks

MT
Mindli Team

AI-Generated Content

React Hooks

React Hooks fundamentally transformed how developers build React applications by enabling state and lifecycle features directly within functional components. Prior to Hooks, managing state or side effects required class components, which often led to complex hierarchies and logic that was difficult to reuse. By embracing Hooks, you can write more concise, readable, and maintainable code while fully leveraging React's functional programming model.

The Foundation: Managing State with useState

The useState Hook is your primary tool for adding local state to a functional component. A Hook is a special function that lets you "hook into" React features like state and lifecycle from a function component. The useState Hook returns an array with two elements: the current state value and a function to update it. Calling this update function triggers a re-render of the component with the new state.

Here is the basic syntax: you call useState and pass the initial state as its only argument. Using array destructuring is the standard way to unpack the returned values.

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>
  );
}

It's important to understand that state updates are asynchronous. If your new state depends on the previous state, you should use the functional update form to ensure accuracy, as multiple rapid updates could otherwise be based on stale values. For example, setCount(prevCount => prevCount + 1) is safer than setCount(count + 1) in certain scenarios, like when batching updates.

Managing Side Effects with useEffect

The useEffect Hook is the central place to perform side effects in your components. Side effects are operations that affect something outside the component's render, such as data fetching, subscriptions, or manually changing the DOM. The useEffect Hook serves the combined purpose of the componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods from class components.

The Hook takes two arguments: a function containing the side-effect logic and an optional dependency array. The behavior of useEffect is governed by this array:

  • No array provided: The effect runs after every render.
  • Empty array []: The effect runs only once, after the initial render (mimicking componentDidMount).
  • Array with dependencies [dep1, dep2]: The effect runs after the initial render and then only re-runs if any of the listed dependencies have changed since the last render.
import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  // Effect depends on the `userId` prop
  useEffect(() => {
    fetch(`/api/user/${userId}`)
      .then(response => response.json())
      .then(data => setUser(data));

    // Optional cleanup function (runs before the next effect or on unmount)
    return () => {
      console.log(`Cleaning up for user ${userId}`);
    };
  }, [userId]); // Effect re-runs only when `userId` changes
}

The cleanup function, returned from the effect, is crucial for preventing memory leaks. It executes before the component unmounts and before running the effect again on subsequent renders, allowing you to cancel network requests or invalidate subscriptions.

Advanced Hooks: useContext and useRef

For more complex scenarios, React provides specialized Hooks. The useContext Hook allows you to subscribe to React context without introducing nested component trees. Context provides a way to pass data through the component tree without having to pass props down manually at every level.

import React, { useContext } from 'react';

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

function ThemedButton() {
  // Consume the current context value
  const theme = useContext(ThemeContext);
  return <button className={theme}>I am styled by theme!</button>;
}

The useRef Hook creates a mutable ref object whose .current property is initialized to the passed argument. The key feature is that mutating .current does not cause a re-render. Its primary uses are: 1) accessing DOM elements directly, and 2) keeping a mutable variable that persists across the entire lifetime of the component, similar to an instance property in a class.

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

function TextInputWithFocusButton() {
  const inputEl = useRef(null);

  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };

  // Using ref to persist a value without causing re-renders
  const renderCount = useRef(0);
  useEffect(() => {
    renderCount.current = renderCount.current + 1;
  });

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
      <p>This component rendered {renderCount.current} times.</p>
    </>
  );
}

Building Reusable Logic with Custom Hooks

Custom Hooks are JavaScript functions whose names start with "use" and that may call other Hooks. They are the ultimate tool for extracting component logic into reusable functions. This allows you to share stateful behavior between components without repeating code or altering your component hierarchy.

A custom Hook is a mechanism to reuse stateful logic, not state itself. Each call to a custom Hook creates completely isolated state. For example, you can extract the logic for subscribing to a friend's online status.

import { useState, useEffect } from 'react';

// Custom Hook
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  }, [friendID]); // Re-subscribe only if friendID changes

  return isOnline;
}

// Using the custom Hook in multiple components
function FriendListItem({ friend }) {
  const isOnline = useFriendStatus(friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {friend.name}
    </li>
  );
}

Common Pitfalls

  1. Incorrect Dependency Arrays in useEffect:
  • Mistake: Omitting dependencies that the effect uses, which can lead to stale data. Conversely, not providing an array at all can cause performance issues by running the effect on every render.
  • Correction: Let your linter (like eslint-plugin-react-hooks) guide you. Include all values from the component scope (props, state, context, and any derivatives) that change over time and that the effect reads. If you want to run an effect only once, use an empty dependency array [].
  1. Stale State in Callbacks and Intervals:
  • Mistake: Using the state variable directly inside a setInterval callback or an event listener defined in useEffect will always reference the state value from the initial render closure.
  • Correction: Use the functional update form for useState or store the latest value in a useRef and read from it. Alternatively, ensure the effect cleans up and re-subscribes whenever the state changes by including it in the dependency array.
  1. Forgetting Cleanup for Ongoing Side Effects:
  • Mistake: Starting a subscription, timer, or network request in useEffect without providing a cleanup function. This causes memory leaks and attempts to update state on an unmounted component.
  • Correction: Always consider if your effect needs cleanup. If your effect sets up something that persists (like a subscription), return a cleanup function that tears it down.
  1. Conditionally Calling Hooks:
  • Mistake: Calling Hooks inside loops, conditions, or nested functions. This violates the Rules of Hooks, which require that Hooks be called at the top level of your React function, in the same order on every render.
  • Correction: Ensure all Hook calls are unconditional. If you need conditional logic, place it inside the Hook.

Summary

  • React Hooks allow you to use state, lifecycle, and other React features in functional components, simplifying your code and eliminating the need for class-based components.
  • useState manages local state, returning the current state value and a setter function you can use to update it and trigger a re-render.
  • useEffect manages side effects (data fetching, subscriptions). Its behavior is controlled by a dependency array, and it can return a cleanup function to prevent memory leaks.
  • useContext provides direct access to React context, allowing you to consume shared values without prop-drilling.
  • useRef creates a mutable object whose .current property persists across renders without causing re-renders, useful for accessing DOM nodes or storing instance-like variables.
  • Custom Hooks are your own functions that start with "use" and call other Hooks, enabling you to extract and reuse complex stateful logic across different components.

Write better notes with AI

Mindli helps you capture, organize, and master any subject with AI-powered summaries and flashcards.