1

I want to render a list and want to make each item clickable.

But when I create a function inside renderItem then on every rerender its recreate. So how I can use clickhandler and memo on this code correctly ?

import { mockAvailableCarsData } from '../../utils/mockData';
import TableDashboardBox from '../TableDashboardBox/TableDashboardBox';
import { IAvailableCarsData } from './types';

const renderItem = (el: IAvailableCarsData) => {
 const navigate = useNavigate();
  return (
    <>
      <tr onClick={() => navigate(`/cars/${el.id}`}  className="meetings-tr">
        <td>{el.car} - {el.mark}</td>
        <td>{el.fuel}</td>
      </tr>
    </>
  )
};

function AvailableCars() {
  return (
    <TableDashboardBox<IAvailableCarsData>
      data={mockAvailableCarsData}
      headerData={['Auto', 'Reichweite']}
      renderItem={renderItem}
      keyExtractor={({id}) => id.toString()}
      title="Available"
      moreLinkURL="/cars"
      createButtonText={null}
      classNameBox="w-100 mt-24 av-cars-box"
    />
  )
}

export default AvailableCars
1
  • Beside the typo here onClick={() => navigate(/cars/${el.id}} missing a ) after el.id}, and that renderItem is a component so should be RenderItem your code looks fine. This feels like a premature optimization. Are you experiencing any performance issues? Commented May 20, 2024 at 0:41

2 Answers 2

1

This is how i would refactor your code:

  • Create component CardData to render list item and wrap it with memo()
  • move headersData array and keyExtractor function outside of the component, to prevent unnecessary re-renders
  • wrap renderItem to useCallback() to prevent unnecessary re-renders

Here is an annotated code.

import { memo, useCallback } from 'react';
import { mockAvailableCarsData } from '../../utils/mockData';
import TableDashboardBox from '../TableDashboardBox/TableDashboardBox';
import { IAvailableCarsData } from './types';

// when using memo we don't need to wrap navigate into useCallback hook.
const CarsData = memo(({ el }: { el: IAvailableCarsData }) => {
  const navigate = useNavigate();
  return (
    <tr onClick={() => navigate(`/cars/${el.id}`)} className="meetings-tr">
      <td>
        {el.car} - {el.mark}
      </td>
      <td>{el.fuel}</td>
    </tr>
  );
});

// move keyExtractor and headerData outside of the component to prevent unnecessary rerenders
const keyExtractor = ({ id }: IAvailableCarsData) => id.toString();
const headerData = ['Auto', 'Reichweite'];

function AvailableCars() {
  // wrap renderItem to useCallback to make sure it is wont change on each rerender.
  const renderItem = useCallback(
    (el: IAvailableCarsData) => <CarsData key={el.id} el={el} />,
    [],
  );
  return (
    <TableDashboardBox<IAvailableCarsData>
      data={mockAvailableCarsData}
      headerData={headerData}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      title="Available"
      moreLinkURL="/cars"
      createButtonText={null}
      classNameBox="w-100 mt-24 av-cars-box"
    />
  );
}

export default AvailableCars;
Sign up to request clarification or add additional context in comments.

Comments

1
import { memo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { mockAvailableCarsData } from '../../utils/mockData';
import TableDashboardBox from '../TableDashboardBox/TableDashboardBox';
import { IAvailableCarsData } from './types';

const RenderItem = memo(({ el }: { el: IAvailableCarsData }) => {
  const navigate = useNavigate();
  const handleClick = useCallback(() => {
    navigate(`/cars/${el.id}`);
  }, [navigate, el.id]);

  return (
    <tr onClick={handleClick} className="meetings-tr">
      <td>{el.car} - {el.mark}</td>
      <td>{el.fuel}</td>
    </tr>
  );
});

function AvailableCars() {
  return (
    <TableDashboardBox<IAvailableCarsData>
      data={mockAvailableCarsData}
      headerData={['Auto', 'Reichweite']}
      renderItem={(el) => <RenderItem key={el.id} el={el} />}
      keyExtractor={({ id }) => id.toString()}
      title="Available"
      moreLinkURL="/cars"
      createButtonText={null}
      classNameBox="w-100 mt-24 av-cars-box"
    />
  );
}

export default AvailableCars;

Try this code. If you have any problem, feel free to reach out me.

4 Comments

This useCallback will have no effect. handleClick is not being passed as a prop to a react component, so there is no danger of re-renders of child components due to the handleClick reference changing.
do you want pass handleClick function as a prop to RenderItem component?
What I mean is we use useCallback to create a stable reference to a function. We do that so child components that take that fuction as a prop won't be forced to re-render because that reference changes in the parent component. Since we have no child components in RenderItem we don't need to use useCallback in this case. It's an optimization to reduce the number of re-renders, but in this case we don't need to worry about that with handleClick in this component.
Here is a quote from the react docs: By wrapping handleSubmit in useCallback, you ensure that it’s the same function between the re-renders (until dependencies change). You don’t have to wrap a function in useCallback unless you do it for some specific reason. In this example, the reason is that you pass it (as a prop) to a component wrapped in memo, and this lets it skip re-rendering. There are other reasons you might need useCallback which are described further on this page. react.dev/reference/react/useCallback

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.