Где повесить обработчик клика для кнопок при маппинге массива в React?
Что проверяют
Понимаете ли вы, как передать в обработчик данные конкретного элемента и когда создание функции на каждый рендер действительно стоит оптимизировать. Следом обычно спрашивают про React.memo, useCallback и про то, зачем нужен key.
Как отвечать
Обработчик объявляют один раз в родительском компоненте, а в map передают ему идентификатор элемента. Рабочие варианты:
- стрелочная функция в JSX:
onClick={() => onSelect(item.id)}. Самый читаемый способ и нормальный по умолчанию, новая функция на каждый рендер стоит копейки; - data-атрибут: один стабильный
handleClick, а id читается изe.currentTarget.dataset.id. Функция не пересоздаётся, но значение приходит строкой, и объект так не передать; - отдельный компонент строки:
<Row item={item} onSelect={onSelect} />, внутри которого кнопка вызываетonSelect(item.id). ЕслиRowобёрнут вReact.memo, аonSelectстабилизирован черезuseCallback, строки не перерисовываются при изменении соседей.
Главная мысль: стрелка внутри map мешает только тогда, когда её получает мемоизированный дочерний компонент, потому что новая ссылка ломает сравнение пропсов. Для обычной <button> разницы нет. Если в проекте включён React Compiler, такую мемоизацию он во многом делает сам.
Про делегирование: React и так вешает слушатели на корневой контейнер, а не на каждую кнопку, поэтому ручной обработчик на обёртке списка ради скорости ничего не даёт.
Про key: нужен стабильный id из данных. Индекс массива ломает состояние строк при сортировке, вставке и удалении.
Где спотыкаются
Пишут onClick={handleClick(item.id)} без обёртки. Функция вызывается во время рендера, а в onClick уходит её результат, чаще всего undefined.
Путают e.target и e.currentTarget: при клике по иконке внутри кнопки target окажется иконкой без data-атрибута.
Оборачивают всё подряд в useCallback, хотя потребитель не обёрнут в React.memo. Кода становится больше, а выигрыша нет.