DEV Community

Strange
Strange

Posted on

Optimizing React Performance: Avoiding Unnecessary Re-renders

Avoiding Re-renders in React

Avoiding unnecessary re-renders in React is essential for optimizing application performance. React's reconciliation process identifies changes in the component tree and updates only the necessary parts. However, improper state management, props passing, or component structure can lead to redundant re-renders.


Techniques to Avoid Unnecessary Re-renders

  1. Use React.memo React.memo prevents re-renders of functional components if their props haven’t changed.
   import React from "react";

   const Child = React.memo(({ name }) => {
     console.log("Child rendered");
     return <div>Hello, {name}</div>;
   });

   function Parent() {
     const [count, setCount] = React.useState(0);

     return (
       <div>
         <button onClick={() => setCount(count + 1)}>Increment</button>
         <Child name="React" />
       </div>
     );
   }

   export default Parent;
Enter fullscreen mode Exit fullscreen mode
  • Without React.memo, Child would re-render every time the parent renders, even if name remains unchanged.
  • React.memo ensures Child renders only when its name prop changes.

  1. Use useCallback for Functions React re-creates functions on every render, which may trigger re-renders if passed as props. Use useCallback to memoize functions.
   import React, { useState, useCallback } from "react";

   const Child = React.memo(({ onClick }) => {
     console.log("Child rendered");
     return <button onClick={onClick}>Click Me</button>;
   });

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

     const handleClick = useCallback(() => {
       console.log("Button clicked");
     }, []);

     return (
       <div>
         <button onClick={() => setCount(count + 1)}>Increment</button>
         <Child onClick={handleClick} />
       </div>
     );
   }

   export default Parent;
Enter fullscreen mode Exit fullscreen mode
  • Without useCallback, handleClick would be recreated on every render, causing Child to re-render.
  • useCallback memoizes handleClick, preventing unnecessary updates.

  1. Use useMemo for Expensive Calculations Use useMemo to memoize computed values and avoid re-calculations on every render.
   import React, { useState, useMemo } from "react";

   function Parent() {
     const [count, setCount] = useState(0);
     const [other, setOther] = useState(0);

     const expensiveCalculation = useMemo(() => {
       console.log("Expensive calculation");
       return count * 2;
     }, [count]);

     return (
       <div>
         <div>Result: {expensiveCalculation}</div>
         <button onClick={() => setCount(count + 1)}>Increment Count</button>
         <button onClick={() => setOther(other + 1)}>Increment Other</button>
       </div>
     );
   }

   export default Parent;
Enter fullscreen mode Exit fullscreen mode
  • useMemo ensures the expensive calculation is re-computed only when count changes.

  1. Avoid Inline Functions and Objects as Props Inline functions and objects are recreated on every render and can trigger re-renders.

Problematic Example:

   const Child = React.memo(({ config }) => {
     console.log("Child rendered");
     return <div>Config: {JSON.stringify(config)}</div>;
   });

   function Parent() {
     const config = { color: "blue" }; // Re-created on every render

     return <Child config={config} />;
   }
Enter fullscreen mode Exit fullscreen mode

Solution: Use useMemo for Objects:

   function Parent() {
     const config = React.useMemo(() => ({ color: "blue" }), []);

     return <Child config={config} />;
   }
Enter fullscreen mode Exit fullscreen mode

  1. Split Components into Smaller Pieces Break large components into smaller, reusable pieces. This limits the scope of updates.
   const Counter = React.memo(({ count }) => {
     console.log("Counter rendered");
     return <div>Count: {count}</div>;
   });

   function App() {
     const [count, setCount] = useState(0);
     const [name, setName] = useState("");

     return (
       <div>
         <Counter count={count} />
         <input
           value={name}
           onChange={(e) => setName(e.target.value)}
           placeholder="Enter name"
         />
         <button onClick={() => setCount(count + 1)}>Increment</button>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode
  • Only Counter will re-render when count changes, not the input field.

  1. Avoid Re-rendering with Context API Context updates trigger re-renders for all consuming components. Use selective context or libraries like zustand or React Query.

Using Selector:

   const CounterContext = React.createContext();

   function CounterProvider({ children }) {
     const [count, setCount] = useState(0);
     return (
       <CounterContext.Provider value={{ count, setCount }}>
         {children}
       </CounterContext.Provider>
     );
   }

   function DisplayCount() {
     const { count } = React.useContext(CounterContext);
     console.log("DisplayCount rendered");
     return <div>Count: {count}</div>;
   }

   function IncrementButton() {
     const { setCount } = React.useContext(CounterContext);
     console.log("IncrementButton rendered");
     return <button onClick={() => setCount((c) => c + 1)}>Increment</button>;
   }

   function App() {
     return (
       <CounterProvider>
         <DisplayCount />
         <IncrementButton />
       </CounterProvider>
     );
   }
Enter fullscreen mode Exit fullscreen mode
  • Here, DisplayCount and IncrementButton render independently.

  1. Avoid Updating Parent State from Child Updating parent state from a child can force the parent and all its children to re-render. Pass handlers sparingly.

  1. Optimize Lists with Keys React needs unique keys for list items to avoid unnecessary re-renders.
   const List = ({ items }) => {
     return (
       <ul>
         {items.map((item) => (
           <li key={item.id}>{item.name}</li>
         ))}
       </ul>
     );
   };
Enter fullscreen mode Exit fullscreen mode

Ensure key is unique and stable.


Tools to Detect Re-renders

  1. React Developer Tools: Inspect which components re-rendered.
  2. why-did-you-render Library: Logs unnecessary re-renders.

Conclusion

Avoiding unnecessary re-renders improves application performance, reduces rendering overhead, and enhances user experience. By understanding React’s rendering behavior and using tools like React.memo, useMemo, and useCallback, you can optimize your components effectively.

Top comments (0)