Ревью и исправление React‑компонента: корректное использование useLayoutEffect и useCallback

Исправьте JSX и имя компонента с заглавной буквы. Подписку создавайте в эффекте с cleanup; не запускайте новый бесконечный интервал на каждый клик и каждый рендер.

Короткий ответ

Что ответить на собеседовании

Исправьте JSX и имя компонента с заглавной буквы. Подписку создавайте в эффекте с cleanup; не запускайте новый бесконечный интервал на каждый клик и каждый рендер.

Подробный разбор

Ответ с пояснениями

Условие

Необходимо провести проверку и доработку React‑компонента, выявив ошибки в применении хуков useLayoutEffect и useCallback, а также избавиться от потенциальных утечек памяти, лишних обработчиков событий и некорректного рендера JSX.

const reviewComponent = () => {
  const [counter, setCounter] = React.useState(1);
  const [list, setList] = React.useState([{ id: 1 }]);
  
  React.useLayoutEffect(() => {
    document.addEventListener("click", () => { 
      setInterval(() => console.log(counter), 1000);
    }); 
  });
  
  const handleClick = React.useCallback(() => { 
    setCounter(counter + 1);
    setList([...list, { id: counter + 1 }]);
  });
  return (
    <React.Fragment>
      <ul>
        (list.map((item) => (
        <li>{item.id)</li>
        ))}
      </ul>
      <button onClick={() => handleClick()}>add one</button> 
    </React.Fragment>
}

Ответ

Исправьте JSX и имя компонента с заглавной буквы. Подписку создавайте в эффекте с cleanup; не запускайте новый бесконечный интервал на каждый клик и каждый рендер.

Один вариант явной логики: первый клик включает логирование счётчика, кнопка добавляет элемент.

function ReviewComponent() {
  const [state, setState] = React.useState({ counter: 1, list: [{ id: 1 }] });
  const [logging, setLogging] = React.useState(false);
  React.useEffect(() => {
    const start = () => setLogging(true);
    document.addEventListener('click', start);
    return () => document.removeEventListener('click', start);
  }, []);
  React.useEffect(() => {
    if (!logging) return;
    const timer = setInterval(() => console.log(state.counter), 1000);
    return () => clearInterval(timer);
  }, [logging, state.counter]);
  const handleClick = React.useCallback(() => setState(previous => {
    const counter = previous.counter + 1;
    return { counter, list: [...previous.list, { id: counter }] };
  }), []);
  return <>
    <ul>{state.list.map(item => <li key={item.id}>{item.id}</li>)}</ul>
    <button onClick={handleClick}>add one</button>
  </>;
}

Интервал пересоздаётся при изменении счётчика и всегда очищается. useLayoutEffect не нужен, потому что измерений DOM до отрисовки нет. Связанные counter/list обновляются одним чистым updater.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее