Использование функции обновления привязки React useState внутри асинхронной функции

#reactjs #react-hooks

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

Вопрос:

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

   const Settings = (props: Props) => {
      const { user } = props;
      const initState: State = {
        newName: user.displayName,
        newAvatarUrl: user.avatarUrl,
        disabled: false,
      };
      const [form, setForm] = useState(initState);

  const handleUploadFile = async () => {
    const fileElement = document.getElementById('upload-file') as HTMLFormElement;
    const file = fileElement.files[0];

    if (file == null) {
      return;
    }
    setForm({ ...form, disabled: true });

    try {
      const response = await asynFunc1()

      await asyncFun2(response)

      setForm({ ...form, newAvatarUrl: response.url });

      await asyncFunc3(form.newAvatarUrl) // Expected: response.url Observed: old value
    } catch (error) {
      consol.log(error)
    } finally {
      setForm({ ...form, disabled: false });
    }
  };

  return (
        <h4>Your Avatar</h4>
        <Avatar
          src={form.newAvatarUrl}
          style={{
            display: 'inline-flex',
            verticalAlign: 'middle',
            marginRight: 20,
            width: 60,
            height: 60,
          }}
        />
        <label htmlFor="upload-file">
          <Button variant="outlined" color="primary" component="span" disabled={form.disabled}>
            Update Avatar
          </Button>
        </label>
        <input
          accept="image/*"
          name="upload-file"
          id="upload-file"
          type="file"
          style={{ display: 'none' }}
          onChange={handleUploadFile}
        />
  );
};
  

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

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

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

2. @Yousaf Спасибо. Я изменил вызов функции, чтобы использовать нужное мне значение и не зависеть от состояния. Представление по-прежнему не отражает обновленное состояние, что означает, что состояние на самом деле не было обновлено правильно?

Ответ №1:

Как упоминалось в комментарии, обновление состояния является асинхронным. Вместо этого вы должны просто передать response.url в вызов asyncFunc3 и установить состояние вашей формы после успешного возврата. Было бы что-то вроде этого:

 const Settings = (props: Props) => {
  const { user } = props;
  const initState: State = {
    newName: user.displayName,
    newAvatarUrl: user.avatarUrl,
    disabled: false,
  };
  const [form, setForm] = useState(initState);

  const handleUploadFile = async () => {
    const fileElement = document.getElementById('upload-file') as HTMLFormElement;
    const file = fileElement.files[0];

    if (file == null) {
      return;
    }
    setForm({ ...form, disabled: true });

    try {
      const response = await asynFunc1()

      await asyncFun2(response)

      await asyncFunc3(response.url)
      
      setForm({ ...form, newAvatarUrl: response.url });
    } catch (error) {
      consol.log(error)
    } finally {
      setForm({ ...form, disabled: false });
    }
  };

  return (
        <h4>Your Avatar</h4>
        <Avatar
          src={form.newAvatarUrl}
          style={{
            display: 'inline-flex',
            verticalAlign: 'middle',
            marginRight: 20,
            width: 60,
            height: 60,
          }}
        />
        <label htmlFor="upload-file">
          <Button variant="outlined" color="primary" component="span" disabled={form.disabled}>
            Update Avatar
          </Button>
        </label>
        <input
          accept="image/*"
          name="upload-file"
          id="upload-file"
          type="file"
          style={{ display: 'none' }}
          onChange={handleUploadFile}
        />
  );
};
  

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

1. Спасибо, это действительно то, что я должен был сделать, чтобы логика была правильной. Представление по-прежнему не отражает обновленное состояние. У меня создалось впечатление, что изменение состояния вызовет повторный рендеринг, но я не уверен, как это программно запустить.

2. Может быть, что пользовательский реквизит также обновляется, и все это повторно отображается? Не могу сказать без дополнительной информации. Возможно, вы могли бы привести небольшой пример на stackblitz.com/edit/react-hr3hhj

3. Спасибо, Виктор. При вводе примера я понял, что это был блок finally, который перезаписывал обновление. stackblitz.com/edit/react-byvglp Есть идеи, как я могу изменить состояние, аналогичное тому, как работает finally?

4. Да, когда вы разрушаете объект состояния так, как вы это делаете там, вы разрушаете свое начальное состояние (таким образом, перезаписываете в своего рода состоянии гонки).). Попробуйте сделать это вместо setState(prevState => ({ ...prevState, flag: false }); вместо этого. Передавая вместо этого функцию, вы принимаете состояние «frame» и не будете использовать устаревший объект состояния

5. Удивительно! Спасибо, Виктор.