DEV Community

Cover image for How to Write React Hooks More Effectively
Shubham_Baghel
Shubham_Baghel

Posted on

How to Write React Hooks More Effectively

1.Only call hooks at the top level: Hooks should only be called at the top level of your component or custom hook. Do not call hooks inside loops, conditions, or nested functions

Example:

function MyComponent() {
  const [state, setState] = useState(0);

  function handleClick() {
    setState(state + 1); // This is correct
  }

  return (
    <button onClick={handleClick}>Click me</button>
  );
}

function MyOtherComponent() {
  const [state, setState] = useState(0);

  function handleClick() {
    if (state > 0) {
      setState(state - 1); // This is correct
    }
  }

  return (
    <button onClick={handleClick}>Click me</button>
  );
}

Enter fullscreen mode Exit fullscreen mode

2.Use the state hook for local state: The state hook is the most commonly used hook and should be used for any local state that your component needs.

function MyComponent() {
  const [name, setName] = useState("");

  return (
    <>
      <input value={name} onChange={e => setName(e.target.value)} />
      <p>Hello, {name}</p>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

3.Use the effect hook for side effects: The effect hook allows you to synchronize a component with an external system, such as a web API or a browser API.

function MyComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("https://my-api.com/data")
      .then(response => response.json())
      .then(data => setData(data));
  }, []);

  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

4.Avoid unnecessary re-renders: Hooks like useMemo and useCallback can help you avoid unnecessary re-renders by memoizing values and functions.

function MyComponent({ items }) {
  const sortedItems = useMemo(() => items.sort(), [items]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

5.Keep hooks simple: Each hook should only handle one piece of logic, such as setting up a subscription or measuring the size of an element

function useData(url) {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => setData(data));
  }, [url]);

  return data;
}

function MyComponent() {
  const data = useData("https://my-api.com/data");

  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

6.Use the context hook to share state: The context hook allows you to share state between components without having to pass props down the component tree.

const MyContext = React.createContext();

function MyProvider({ children }) {
  const [name, setName] = useState("");

  return (
    <MyContext.Provider value={{ name, setName }}>
      {children}
    </MyContext.Provider>
  );
}

function MyComponent() {
  const { name, setName } = useContext(MyContext);

  return (
    <>
      <input value={name} onChange={e => setName(e.target.value)} />
      <p>Hello, {name}</p>
    </>
  );
}

function App() {
  return (
    <MyProvider>
      <MyComponent />
    </MyProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the MyProvider component creates a context and provides the name and setName state to its children via the value prop. The MyComponent component then uses the useContext hook to access the name and setName state from the context.

This way, the name state can be accessed and modified by any component that is a child of the MyProvider component, without having to pass props down the component tree.

7.The handleClick function uses the callback form of the setCount updater, which receives the previous state and returns the new state. This way, the function can update the state based on the previous value, without needing to use a useEffect or useRef to store the previous value.

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

  const handleIncrement = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, [setCount]);

  const handleDecrement = useCallback(() => {
    setCount(prevCount => prevCount - 1);
  }, [setCount]);

  return (
    <>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
      <p>Count: {count}</p>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

This approach is useful in situations where the component updates frequently, and you want to prevent unnecessary re-renders caused by changing function references. It also allows to memoize the function, which means it will not recreate the function if the dependencies haven't changed.

8.Use the useEffect hook with a cleanup function: A cleanup function is a function that is executed when a component is unmounted or a hook is re-run

import { useEffect, useState } from 'react';

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

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count => count + 1);
    }, 1000);
    // Cleanup function
    return () => clearInterval(intervalId);
  }, [setCount]);

  return <p>Count: {count}</p>;
}

Enter fullscreen mode Exit fullscreen mode

In this example, the useEffect hook is used to set up an interval that increments the count state every second. The setInterval function returns an interval ID, which is stored in a variable called intervalId.

The useEffect hook also takes a function as a second argument that is used as a cleanup function. It is executed when the component is unmounted or the hook is re-run. In this case, the cleanup function is used to clear the interval by passing in the intervalId variable to the clearInterval function.

This way, we can ensure that the interval is properly cleared when the component is no longer in use, and avoid memory leaks.

Cleanup functions are a powerful feature of the useEffect hook, they allow you to perform a cleanup operation when a component is unmounted or when a hook is re-run. This makes sure that you don't leave any unnecessary event listeners, timeouts, intervals or other resources that are no longer needed.

9.Always pass the dependencies array to useEffect: This array should include all values that the effect depends on.

import { useEffect, useState } from 'react';

function MyComponent({ userId }) {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then(response => response.json())
      .then(data => setUserData(data));
  }, [userId, setUserData]);

  return userData ? (
    <>
      <h2>{userData.name}</h2>
      <p>{userData.email}</p>
    </>
  ) : (
    <p>Loading...</p>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the useEffect hook is used to fetch data from an API based on the userId prop passed to the component. The fetch function is used to make a request to the API, and the then method is used to handle the response and update the userData state.

The second argument passed to the useEffect hook is an array of dependencies, which includes userId and setUserData. These are the values that the effect depends on, so they should be included in the dependencies array.

This way, the effect will only re-run when the userId prop or setUserData state change, and it will not re-run when other props or state values change. This can help to optimize the performance of your component and avoid unnecessary re-renders.

Always passing the dependencies array to the useEffect hook is important because it tells React when the effect should be run again. If you don't specify the dependencies array, React will assume that the effect depends on all the state and props, and will run the effect on every render. This can lead to unexpected behavior and performance issues.

10.Be careful when using hooks in loops: If you want to use a hook in a loop, make sure that each rendered element has its own state

import { useState, useEffect } from "react";

function MyComponent({ items }) {
  return (
    <ul>
      {items.map((item, index) => {
        const [isSelected, setIsSelected] = useState(false);
        return (
          <li key={item.id} onClick={() => setIsSelected(!isSelected)}>
            {item.name} {isSelected ? "Selected" : ""}
          </li>
        );
      })}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

In this example, My component takes an items prop and maps over it to render a list of items. Each item is rendered as a list item (li) element. Inside the map function, I am using the useState hook to create a isSelected state variable for each item.

By providing a unique key for each item, React can tell which elements are being added, removed, or re-arranged. This way, React can manage the state of each element separately, and you don't have to worry about the hook state being shared across elements.

It is important to be careful when using hooks inside loops, because if you don't provide a unique key for each element, React will not be able to tell which elements are being added, removed or re-arranged, and it will reuse the state of the hook across elements, which can lead to unexpected behavior and bugs.

It's also important to mention that we could use useState inside a callback or inside useEffect instead of directly in the JSX, this way we don't have to worry about different components using the same state.

Top comments (2)

Collapse
 
brense profile image
Rense Bakker

Number 8 is so so so important. Still too often you see people do async stuff inside useEffect without any cleanup. Another common problem I still see a lot is when people use the useEffect hook for derived state, instead of the useMemo hook.

Collapse
 
shubhamb profile image
Shubham_Baghel • Edited

Thanks @brense yeah its really make sense ,it most important thing to keep in mind while development