Реагировать на хук useEffect, ссылающийся на неверные значения

Я работаю над созданием компонента для внешней библиотеки, которую хочу использовать в своем проекте (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.info(min, max);
}

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

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

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

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

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
966
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

Замените 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)
    • Другой обновляет только конечное событие, когда изменяется props.onEnd (что на самом деле... при каждом рендере, см. index.js > handleEnd). Не забудьте очистить прослушиватель событий.

Это ломает приложение.

Adam 05.03.2019 22:36

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

ostrebler 05.03.2019 22:37

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

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

@Strebler Блоки if (!sliderElement.current) не нужны ни в useEffect.

Adam 05.03.2019 23:06

@ Адам Хорошо, я просто сохранил исходный код с этими проверками. Я отредактирую для более чистого ответа.

ostrebler 05.03.2019 23:09

Я заметил, что это решение вызывает экспоненциальное срабатывание событий при каждом изменении ползунка, и в конечном итоге конечное значение не обновляется. Это может быть проблемой с пакетом noUiSlider, который использует OP.

Adam 05.03.2019 23:12

Да только что тоже самое видел. Я готов.

ostrebler 05.03.2019 23:14

@ Адам Исправлено. Просто нужно было очистить событие end во втором onEffect. Проверьте мое обновление.

ostrebler 05.03.2019 23:17

Другие вопросы по теме