Перехват React useEffect, ссылающийся на неправильные значения

#javascript #reactjs #react-hooks

#javascript #reactjs #реагирующие перехваты

Вопрос:

Я работаю над созданием компонента для внешней библиотеки, которую я хочу использовать в своем проекте (noUiSlider).

В настоящее время у меня есть это как мой Slider компонент:

 function Slider(props) {
  const { defaultMin, defaultMax, step } = props;
  const sliderElement = useRef();

  useEffect(() => {
    if (!sliderElement.current) {
      return;
    }

    noUiSlider.create(sliderElement.current, {
      connect: true,
      start: [defaultMin, defaultMax],
      step,
      range: {
        min: [defaultMin],
        max: [defaultMax],
      },
    });

    if (props.onUpdate) {
      sliderElement.current.noUiSlider.on('update', props.onUpdate);
    }

    if (props.onEnd) {
      sliderElement.current.noUiSlider.on('end', props.onEnd);
    }
  }, []);

  return <div ref={sliderElement} />;
}
  

здесь я жду, пока элемент DOM станет доступным, затем создаю экземпляр noUiSlider для этого элемента DOM ( div я возвращаюсь). Я также прикрепляю некоторые обработчики событий внутри эффекта (onUpdate и onEnd).

Slider Компонент используется следующим образом…

 // custom hook that just sets state
const [min, max, setMinMax] = useSlider(10, 300);

const handleEnd = () => {
  // these are outdated :(
  console.log(min, max);
}

<Slider 
  defaultMin={10} 
  defaultMax={300} 
  step={10} 
  onEnd={handleEnd} 
  onUpdate={setMinMax} 
/>
  

Проблема, с которой я сталкиваюсь, заключается в том, что внутри, handleEnd когда я хочу ссылаться на min и max , они являются начальными значениями, а не значениями, которые я ожидал обновить.

Как мне извлечь обновленные значения из той же области из функции, которую я вызываю внутри useEffect ?

Я создал пример codesandbox, чтобы показать это в действии.

Ответ №1:

Основная проблема возникает из-за того, как вы вызываете useEffect в своем Slider компоненте. Вы добавили [] в качестве второго параметра, который не позволяет вызывать ваш эффект более одного раза. Таким образом, он вызывается только один раз после первого рендеринга, а затем никогда больше, и поэтому он сохраняет старое onEnd , сохраняя устаревшие значения для min и max.

Из документа React :

Если вы хотите запустить эффект и очистить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив ([]) в качестве второго аргумента. Это сообщает React, что ваш эффект не зависит ни от каких значений из props или state, поэтому его никогда не нужно запускать повторно.

Замените Slider.js этим, и это сработает :

 import React, { useEffect, useRef } from 'react';
import noUiSlider from 'nouislider';

function Slider(props) {
  const { defaultMin, defaultMax, step } = props;
  const sliderElement = useRef();

  useEffect(() => {
    noUiSlider.create(sliderElement.current, {
      connect: true,
      start: [defaultMin, defaultMax],
      step,
      range: {
        min: [defaultMin],
        max: [defaultMax],
      },
    });

    if (props.onUpdate) {
      sliderElement.current.noUiSlider.on('update', props.onUpdate);
    }
  }, []);

  useEffect(
    () => {
      if (props.onEnd) {
        sliderElement.current.noUiSlider.on('end', props.onEnd);
      }
      return () => {
        sliderElement.current.noUiSlider.off('end');
      };
    },
    [props.onEnd],
  );

  return <div ref={sliderElement} />;
}

export default Slider;
  

Некоторые замечания здесь :

  • props.onChange никогда не определяется и не используется в вашем Slider компоненте. Я отбросил его.
  • useEffect должен быть разделен на две useEffect части :
    • Один обрабатывает создание слайдера и подключает функцию обновления (которую не нужно обновлять, поскольку она всегда сохраняет действительную ссылку на setMinMax в useSlider.js )
    • Другой обновляет только событие end, когда props.onEnd изменяется (что на самом деле … при каждом рендеринге, см. index.js > handleEnd ). Не забудьте очистить прослушиватель событий.

Комментарии:

1. Это прерывает работу приложения.

2. Тогда у вас возникнут другие проблемы (я проверю), но это, безусловно, правильное начало.

3. Спасибо за мысль @Strebler. К сожалению, у меня он отображается только один раз, потому что в противном случае он повторно создавал бы экземпляр noUiSlider каждый раз при повторном отображении. Я попытался поместить свои реквизиты в этот массив (т. Е. [props.onEnd, props.onUpdate] ), но поскольку сами эти функциональные реквизиты не обновляются, это, по сути, то же самое, что [] .

4. props.onEnd действительно обновляется, поскольку вы воссоздаете функцию при каждом рендеринге в index.js . Я пытаюсь разделить ваш файл useEffect на две useEffect части. Давайте посмотрим, к чему это приведет.

5. @Strebler if (!sliderElement.current) Блоки не нужны ни в том, ни в другом useEffect случае.