Хук useEffect не срабатывает после изменения состояния

У меня есть два родственных компонента, которые разделяют состояние через контекст в реакции. Общее состояние между компонентами — это массив.

Если я обновлю состояние arr в одном компоненте, я хочу, чтобы другой компонент прослушивал это обновление и делал что-то соответственно. Когда я использую useEffect во втором компоненте, я слушаю изменения в переменной состояния arr.

Например:

// App Component -------
const App = props => {
 const { arr, setArr } = useContext(GlobalContext)

 const handleChangeEvent = () => {
   const newArr = arr
   [10, 20, 30, 40].map(v => {
     newArr.push(v)
     setArr(newArr)
   })

  return (...)
}

// App2 (Sibling) Component 
const App2 = props => {
  const { arr, setArr } = useContext(GlobalContext)
  const [localArr, setLocalArr] = useState(0)

  useEffect(
    () => {
      updateLocalState()
    },
    // fire if "arr" gets updated
    [arr]
  )

  const updateLocalState = () => {
    setLocalArr(localArr + 1)
  }

  return (...)
}

Хук useEffect — это Только, запускаемый при начальном рендеринге, хотя состояние arr обновляется.

Я знаю, что объявление новой переменной const newArr = arr в моей переменной состояния является ссылкой, поэтому newArr.push(v) технически является мутацией состояния. Однако состояние по-прежнему обновляется, никаких предупреждений не выдается, а useEffect ничего не делает.

Почему useEffect не вызывается, хотя состояние обновляется? Это из-за мутации состояния?

Второй вопрос: почему нет предупреждений или ошибок относительно мутации состояния? Мутации состояния опасны. Если это произойдет, я ожидаю какого-то предупреждения.

Живая демонстрация здесь:

Хук useEffect не срабатывает после изменения состояния

Вы никогда не создаете новый массив. const newArr = arr ссылается на тот же массив с другой переменной. Попробуйте, например. Вместо const newArr = [...arr].

Tholle 10.02.2019 21:58

Это ссылка на тот же массив, но это не объясняет, почему useEffect не срабатывает, хотя состояние все еще обновляется.

lsimonetti 10.02.2019 21:59

Массив, который вы передаете в качестве второго аргумента в useEffect, только проверяет, являются ли элементы в массиве === элементами в нем в предыдущем рендеринге. const newArr = arr; приведет к newArr === arr, а это не то, что вам нужно.

Tholle 10.02.2019 22:01

что говорит @Tholle, просто изменение setArr([...newArr]) запускает useEffect правильно

Artyom Neustroev 10.02.2019 22:02
Поведение ключевого слова "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) для оценки ваших знаний,...
21
4
27 112
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Массив, который вы передаете в качестве второго аргумента в useEffect, только проверяет, являются ли элементы в массиве === элементами в нем в предыдущем рендеринге. const newArr = arr; приведет к newArr === arr, поскольку он не создает новый массив, а это не то, что вам нужно.

Создайте новый массив со всеми элементами в arr, и он будет работать как положено.

const App = props => {
 const { arr, setArr } = useContext(GlobalContext)

 const handleChangeEvent = () => {
   const newArr = [...arr]
   [10, 20, 30, 40].forEach(v => {
     newArr.push(v)
   })
   setArr(newArr)
 }

  return <>{/* ... */}</>
}

Я думаю, что, возможно, стоит продемонстрировать лучшую практику для этого блока кода — что-то такое же простое, как setArr([...arr, 10, 20, 30, 40]).

Artyom Neustroev 10.02.2019 22:09

Я не считал, что алгоритм сравнения в useEffect является строгим сравнением и является одним и тем же объектом для этого сравнения. В этом есть смысл.

lsimonetti 10.02.2019 22:15

Интересно, почему не возникает никаких ошибок или предупреждений о мутации состояния?

lsimonetti 10.02.2019 22:15

@lsimonetti У React нет прямого способа проверить наличие мутаций, не говоря уже о предупреждении, когда вы это делаете, к сожалению. Это просто ограничение языка.

Tholle 10.02.2019 22:16

Если вы хотите обновить массив с помощью хука useState. Обязательно распространите массив на новый массив и обновите новый массив, чтобы ваш useEffect прослушивал это состояние.

UseEffect не позвонит в приведенном ниже фрагменте кода, поскольку вы напрямую обновляете массив.

const [skills, selectedSkills] = useState([])

     const onSelect = (selectedList) => {
            selectedSkills(selectedList)
        }
     useEffect(() => {
            MyLogger('useEffect called')
        }, [skills])

UseEffect позвоню в приведенном ниже фрагменте кода, поскольку мы сохраняем новую ссылку на массив.

const [skills, selectedSkills] = useState([])

     const onSelect = (selectedList) => {
            const tempSelectedList = [...selectedList]
            selectedSkills(tempSelectedList)
        }
     useEffect(() => {
            MyLogger('useEffect called')
        }, [skills])

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