Challenge: Optimize a React Component

You have a simple React component that displays a list of numbers, along with a button that allows you to increase a counter. The list of numbers is filtered based on whether they are even or odd. The problem is that this component re-renders unnecessarily, even when the counter changes.

Your task is to optimize the component using useMemo and useCallback.

Here’s the unoptimized component:

import React, { useState } from 'react';

const NumberList = ({ numbers, filterEven }) => {
  const filteredNumbers = filterEven
    ? numbers.filter(num => num % 2 === 0)
    : numbers;

  console.log('NumberList rendered');

  return (
    <ul>
      {filteredNumbers.map(num => (
        <li key={num}>{num}</li>
      ))}
    </ul>
  );
};

const App = () => {
  const [count, setCount] = useState(0);
  const [filterEven, setFilterEven] = useState(true);

  const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Increase Count ({count})
      </button>

      <button onClick={() => setFilterEven(!filterEven)}>
        Toggle Even Filter (Currently {filterEven ? 'Even' : 'All'})
      </button>

      <NumberList numbers={numbers} filterEven={filterEven} />
    </div>
  );
};

export default App;

Requirements:

  1. Use useMemo to prevent the filteredNumbers from being recalculated on every render.
  2. Use useCallback to avoid re-creating the filterEven logic every time the component renders.

Your Task:

Make sure that NumberList only re-renders when necessary.

Refactor the code to use useMemo and useCallback where appropriate.

Here’s what can be changed and improved.

  1. Move NumberList outside the App component:
    useCallback is used to memoize the function, but since NumberList is a component and defined inside App, it still gets re-created on every render. It’s better to move NumberList outside of App.
  2. Use useMemo for filtering numbers:
    The filtered numbers list should be memoized using useMemo, as the filtering operation itself is what’s expensive. useCallback isn’t necessary for the NumberList component, but useMemo should be used to optimize the filtered list.

Here’s an improved version of your code:

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

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const NumberList = ({ numbers }) => {
  console.log('NumberList rendered');

  return (
    <ul>
      {numbers.map(num => (
        <li key={num}>{num}</li>
      ))}
    </ul>
  );
};

const App = () => {
  const [count, setCount] = useState(0);
  const [filterEven, setFilterEven] = useState(true);
  
  // Memoize the filtered numbers
  const filteredNumbers = useMemo(() => {
    return filterEven
      ? numbers.filter(num => num % 2 === 0)
      : numbers;
  }, [filterEven, numbers]);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Increase Count ({count})
      </button>

      <button onClick={() => setFilterEven(!filterEven)}>
        Toggle Even Filter (Currently {filterEven ? 'Even' : 'All'})
      </button>

      {/* Pass memoized filtered numbers */}
      <NumberList numbers={filteredNumbers} />
    </div>
  );
};

export default App;

Changes made:

  1. NumberList is moved outside App: This prevents NumberList from being re-created on every render.
  2. useMemo for filteredNumbers: The filtering logic is now memoized, meaning it will only recalculate the filtered list if filterEven changes.

How it works now:

  • The NumberList component only re-renders when the list of filtered numbers changes.
  • The expensive filtering operation is optimized using useMemo, ensuring it only runs when necessary.

This version should solve the unnecessary re-rendering issue while properly utilizing useMemo for performance optimization. Let me know if this helps or if you have more questions!

Leave a Reply

Your email address will not be published. Required fields are marked *