React.useState не перезагружает состояние из реквизита

Я ожидаю перезагрузки состояния при изменении реквизита, но это не работает, и переменная user не обновляется при следующем вызове useState, что не так?

function Avatar(props) {
  const [user, setUser] = React.useState({...props.user});
  return user.avatar ? 
         (<img src = {user.avatar}/>)
        : (<p>Loading...</p>);
}

кодовая ручка

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
182
0
91 266
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Согласно Документация ReactJS о хуках :

But what happens if the friend prop changes while the component is on the screen? Our component would continue displaying the online status of a different friend. This is a bug. We would also cause a memory leak or crash when unmounting since the unsubscribe call would use the wrong friend ID.

Ваше единственное взаимодействие здесь должно происходить при смене реквизита, что, похоже, не работает. Вы можете (по-прежнему в соответствии с документом) использовать componentDidUpdate(prevProps) для упреждающего обнаружения любых обновлений реквизита.

PS: у меня недостаточно кода, чтобы судить, но не могли бы вы активно setUser() внутри вашей функции Avatar(props)?

Параметр, переданный в React.useState(), является только начальным значением для этого состояния. React не распознает это состояние как изменение, а только установит его значение по умолчанию. Сначала вы захотите установить состояние по умолчанию, а затем условно вызвать setUser(newValue), которое будет распознано как новое состояние, и выполнить повторную визуализацию компонента.

Я бы рекомендовал с осторожностью обновлять состояние без каких-либо условий, чтобы предотвратить его постоянное обновление и, следовательно, повторную визуализацию каждый раз при получении реквизита. Возможно, вы захотите рассмотреть возможность переноса функциональности состояния на родительский компонент и передачи состояния родителя этому аватару в качестве опоры.

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

Аргумент, передаваемый в useState, является начальным состоянием, очень похожим на настройку состояния в конструкторе для компонента класса, и не используется для обновления состояния при повторном рендеринге.

Если вы хотите обновить состояние при изменении реквизита, используйте useEffect хук

function Avatar(props) {
  const [user, setUser] = React.useState({...props.user});

  React.useEffect(() => {
      setUser(props.user);
  }, [props.user])

  return user.avatar ? 
         (<img src = {user.avatar}/>)
        : (<p>Loading...</p>);
}

Рабочая демонстрация

При установке начального состояния в конструкторе понятно, что конструктор вызывается один раз при создании экземпляра класса. С хуками похоже, что useState вызывается каждый раз при вызове функции и каждый раз он должен инициализировать состояние, но там происходит какая-то необъяснимая «магия».

vitalyster 25.02.2019 13:31

Может быть, этот пост прольет на это свет stackoverflow.com/questions/54673188/…

Shubham Khatri 25.02.2019 13:32

если вам нужно объединить реквизиты в состояние, вы застрянете в бесконечном цикле при использовании приложения create-реагировать, которое требует, чтобы состояние было в массиве зависимостей

user210757 29.07.2019 18:42

Это заканчивается установкой начального состояния дважды, что довольно раздражает. Интересно, можете ли вы установить начальное состояние равным нулю, а затем использовать React.useEffect для установки начального состояния каждый раз.

CMCDragonkai 13.08.2019 07:08

Это не работает, если состояние также управляет рендерингом. У меня есть функциональный компонент, который рендерится на основе реквизита и рендерится на основе состояния. Если состояние указывает компоненту на рендеринг, потому что он изменился, тогда запускается useEffect и возвращает состояние к значению по умолчанию. Это действительно плохой дизайн с их стороны.

Greg Veres 09.02.2020 03:56

Я был в том же случае, о котором упоминал @GregVeres, - я хотел создать оптимистичную кнопку переключения. Должно быть так просто, но с новыми хуками React 16.3 устарел, как мы могли сделать это чистым способом. Решение — задокументированный взлом: заставить React воссоздать ваш компонент, связав его ключ с тем, что должно измениться внутри него. Таким образом, вы гарантированно сбросите состояние при изменении соответствующих реквизитов! Не уверен, смеюсь я или плачу.

igorsantos07 28.04.2020 10:42

@igorsantos07 igorsantos07 Состояние возвращается к изменению значения реквизита только в том случае, если изменяется props.user, а не иначе.

Shubham Khatri 28.04.2020 10:48

@CMCDragonkai, вы можете использовать ссылку, чтобы пропустить первоначальный рендеринг после монтирования компонента: stackoverflow.com/questions/53179075/…

Chunky Chunk 11.07.2020 22:15

Почему вы используете React.useState({...props.user}) вместо просто React.useState(props.user)?

Robo Robok 12.11.2020 23:49

Попали в ту же ловушку бесконечного цикла обновления компонентов, когда рендеринг зависел от реквизита и состояния. Решается путем разделения компонента на внешний и внутренний компоненты. Внешний компонент вычисляет начальное состояние для внутреннего компонента только на основе внешних реквизитов. Затем начальное состояние передается внутреннему компоненту через реквизит и используется для инициализации внутреннего состояния. Обновление внутренних состояний, когда не вызывает бесконечный цикл, но изменение внешних реквизитов корректно повторно инициализирует внутренние.

TheDiveO 04.12.2020 20:55

Тогда в чем разница от использования ленивой версии React.useState, которая принимает лямбда?

Ramesh 01.02.2021 09:31

Можете ли вы объяснить, в чем необходимость использования useState внутри компонента аватара, поскольку он зависит от переданных реквизитов и никогда не меняется внутри компонента? Почему бы не использовать напрямую props.user ? Компонент получает повторный рендеринг даже при изменении реквизита.

Antonio Pantano 17.10.2021 09:57

@AntonioPantano, вы можете напрямую использовать props.user, когда дочерний компонент временно не изменяет реквизит локально. Приведенное выше решение предназначено для демонстрации того, как вы можете справиться с этим, если есть требование, подобное этому.

Shubham Khatri 20.10.2021 11:10

Функциональные компоненты, в которых мы используем useState для установки начальных значений нашей переменной, если мы передаем начальное значение через реквизит, оно всегда будет устанавливать одно и то же начальное значение, пока вы не используете useEffect хук,

например, ваш случай, это сделает вашу работу

 React.useEffect(() => {
      setUser(props.user);
  }, [props.user])

Функция, переданная в useEffect, будет запущена после фиксации рендеринга на экране.

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

React.useEffect(FunctionYouWantToRunAfterEveryRender)

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

React.useEffect(FunctionYouWantToRunAfterEveryRender, [props.user])

как вы заметили, я передаю [props.user] сейчас useEffect будет запускать эту FunctionYouWantToRunAfterEveryRender функцию только при изменении props.user

я надеюсь, что это поможет вашему пониманию дайте мне знать, если требуются какие-либо улучшения, спасибо

Я создал такие пользовательские хуки:

const usePrevious = value => {
   const ref = React.useRef();

   React.useEffect(() => {
       ref.current = value;
   }, [value]);

   return ref.current;
}

const usePropState = datas => {
    const [dataset, setDataset] = useState(datas);
    const prevDatas = usePrevious(datas);

    const handleChangeEventDataset = data => setDataset(data);

    React.useEffect(() => {
        if (!deepEqual(datas, prevDatas)) // deepEqual is my function to compare object/array using deep-equal
            setDataset(datas);
    }, [datas, prevDatas]);

    return [
        dataset,
        handleChangeEventDataset
    ]
}

Использовать:

const [state, setState] = usePropState(props.datas);

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

https://gist.github.com/mahmut-gundogdu/193ad830be31807ee4e232a05aeec1d8

    import {useEffect, useState} from 'react';

    export function useStateWithDep(defaultValue: any) {
      const [value, setValue] = useState(defaultValue);
    
      useEffect(() => {
        setValue(defaultValue);
      }, [defaultValue]);
      return [value, setValue];
    }

#Пример

const [user, setUser] = useStateWithDep(props.user);

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