Реагировать на хук useState, который не обновляется в async useEffect

Я хочу перезаписать пользователя объектом, который я сохранил в localStorage, но он не работает.

Обратите внимание: компонент называется Index, потому что я использую next.js

import React, { useState, useEffect } from 'react';
import { loadUser } from '../utils/user';

const Index = () => {
  const [ user, setUser ] = useState({});

  async function restore(){
    const loadedUser = await loadUser();
    console.log(loadedUser);                    // correct - {userId: 4124783261364}
    setUser(loadedUser); 
    console.log(user);                          // was not updated - {}
  };

  useEffect(() => {
    restore();
  }, [0])

  return (<></>);
};

export default Index;

person JamesK    schedule 28.10.2020    source источник
comment
Обновления состояния могут быть асинхронными. Вы не можете получить обновленного пользователя сразу после setUser. React может объединять несколько вызовов setState () в одно обновление для повышения производительности.   -  person Eric    schedule 29.10.2020


Ответы (2)


Проблемы

  • Если вы хотите запустить эффект и очистить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив ( []) (не [0]) в качестве второго аргумента. Это сообщает React, что ваш эффект не зависит от каких-либо значений из свойств или состояния, поэтому его никогда не нужно повторно запускать.
  • Подобно setState в компонентах класса, созданных путем расширения React.Component или React.PureComponent, обновление состояния с помощью средства обновления, предоставляемого обработчиком useState, также является асинхронным, и будет не отражаются немедленно. Вот почему console.log(user); не показывает обновленное значение рядом с setUser(loadedUser);

Решение

  const restore = async () => {
    const loadedUser = await loadUser();
    console.log(loadedUser);                    // correct - {userId: 4124783261364}
    setUser(loadedUser); 
    console.log(user);                          // user is not updated here, but will be
  };

  useEffect(() => {
    restore();
  }, []);

Аналогичная ситуация

Это далеко от вашей проблемы, поскольку вам просто нужно загрузить пользователя из локального хранилища только один раз. Но когда loadUser() возвращает изменяющиеся результаты по времени, поэтому необходимо обновлять пользователя в реальном времени, следует использовать useCallback.

  const updateUser = useCallback(async () => {
    const loadedUser = await loadUser();
    setUser(loadedUser); 
  }, []);

  useEffect(() => {
    updateUser();
  }, [updateUser]);
person koko-js478    schedule 29.10.2020
comment
Спасибо @ softengineer123, я не знал, что setState был асинхронным. Это объясняет, почему я позже заметил, что это консольное логирование. Кроме того, я начал с пустого массива dep, но добавил 0, чтобы посмотреть, поможет ли это. Я вернусь. - person JamesK; 30.10.2020

Попробуйте что-то вроде этого:

// Assuming your loadUser() functions is set like the line below
const loadedUser = {userID: '513216351354'};


const Index = () => {

function restore(){
  console.log("Loaded user is " + loadedUser.userID);       // Loaded user is 513216351354
  setUser(loadedUser.userID); 
  console.log("User is " + user);                          // User is 513216351354
};

useEffect(() => {
  restore();
}, [])

return (<></>);
}

export default Index;
person Leonardo Noronha Santos    schedule 29.10.2020
comment
Я не понимаю, как это решит проблему асинхронности setUser. Кроме того, пользователь должен быть объектом, потому что со временем у него будет больше свойств. - person JamesK; 30.10.2020