Как убедиться, что состояние React с использованием ловушки useState () было обновлено? - PullRequest
8 голосов
/ 25 марта 2019

У меня был компонент класса с именем <BasicForm>, с помощью которого я создавал формы. Он обрабатывает проверку и все формы state. Он предоставляет все необходимые функции (onChange, onSubmit и т. Д.) Для входов (отображается как children из BasicForm) через контекст React.

Работает так, как задумано. Проблема в том, что теперь, когда я конвертирую его в React Hooks, у меня возникают сомнения при попытке воспроизвести следующее поведение, которое я делал, когда это был класс:

class BasicForm extends React.Component {

  ...other code...

  touchAllInputsValidateAndSubmit() {

    // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
    let inputs = {};
    for (let inputName in this.state.inputs) {
      inputs = Object.assign(inputs, {[inputName]:{...this.state.inputs[inputName]}});
    }

    // TOUCH ALL INPUTS
    for (let inputName in inputs) {
      inputs[inputName].touched = true;
    }

    // UPDATE STATE AND CALL VALIDATION
    this.setState({
      inputs
    }, () => this.validateAllFields());  // <---- SECOND CALLBACK ARGUMENT
  }

  ... more code ...

}

Когда пользователь нажимает кнопку отправки, BasicForm должен «коснуться» всех входов и только потом вызывать validateAllFields(), потому что ошибки проверки будут отображаться только при касании ввода. Так что, если пользователь не коснулся ни одной, BasicForm необходимо убедиться, что он "прикоснулся" к каждому входу перед вызовом функции validateAllFields().

И когда я использовал классы, я использовал второй аргумент обратного вызова в функции setState(), как видно из приведенного выше кода. И это обеспечило вызов validateAllField() только после обновления состояния (того, которое касается всех полей).

Но когда я пытаюсь использовать этот второй параметр обратного вызова с хуками состояния useState(), я получаю эту ошибку:

const [inputs, setInputs] = useState({});

... some other code ...

setInputs(auxInputs, () => console.log('Inputs updated!'));

Предупреждение: обновления состояния из хуков useState () и useReducer () не поддерживает второй аргумент обратного вызова. Выполнить побочный эффект после рендеринга объявите его в теле компонента с помощью useEffect ().

Итак, согласно сообщению об ошибке выше, я пытаюсь сделать это с помощью useEffect() hook. Но это немного смущает меня, потому что, насколько я знаю, useEffect() основан не на обновлениях состояний, а на выполнении рендеринга. Он выполняется после каждого рендера. И я знаю, что React может поставить в очередь некоторые обновления состояния перед повторным рендерингом, поэтому я чувствую, что не могу полностью контролировать, когда мой хук useEffect() будет выполнен так, как я это делал, когда использовал классы и setState() Второй аргумент обратного вызова.

То, что я до сих пор получил (кажется, работает):

function BasicForm(props) {

  const [inputs, setInputs] = useState({});
  const [submitted, setSubmitted] = useState(false);

  ... other code ...

  function touchAllInputsValidateAndSubmit() {
    const shouldSubmit = true;

    // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
    let auxInputs = {};
    for (let inputName in inputs) {
      auxInputs = Object.assign(auxInputs, {[inputName]:{...inputs[inputName]}});
    }

    // TOUCH ALL INPUTS
    for (let inputName in auxInputs) {
      auxInputs[inputName].touched = true;
    }

    // UPDATE STATE
    setInputs(auxInputs);
    setSubmitted(true);
  }

  // EFFECT HOOK TO CALL VALIDATE ALL WHEN SUBMITTED = 'TRUE'
  useEffect(() => {
    if (submitted) {
      validateAllFields();
    }
    setSubmitted(false);
  });

  ... some more code ...

}

Я использую хук useEffect() для вызова функции validateAllFields(). И так как useEffect() выполняется на каждом рендере , мне нужен был способ узнать, когда вызывать validateAllFields(), так как я не хочу этого на каждом рендере. Таким образом, я создал переменную состояния submitted, чтобы знать, когда мне нужен этот эффект.

Это хорошее решение? Какие еще возможные решения вы могли бы подумать? Это просто странно.

Представьте, что validateAllFields() - это функция, которая НЕ МОЖЕТ быть вызвана дважды без каких-либо обстоятельств. Как мне узнать, что при следующем рендеринге мое submitted состояние будет уже «ложным» на 100% уверенным?

Могу ли я рассчитывать на то, что React выполнит каждое обновление состояния в очереди перед следующим рендерингом? Это гарантировано?

Ответы [ 2 ]

2 голосов
/ 25 марта 2019

Я недавно столкнулся с чем-то подобным (ТАК вопрос здесь ), и кажется, что вы придумали достойный подход.

Вы можете добавить аргумент к useEffect(), который должен делать то, что вы хотите:

например.

useEffect(() => { ... }, [submitted])

следить за изменениями в submitted.

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

import React, { useState, useCallback } from 'react';

const useStateful = initial => {
  const [value, setValue] = useState(initial);
  return {
    value,
    setValue
  };
};

const useSetState = initialValue => {
  const { value, setValue } = useStateful(initialValue);
  return {
    setState: useCallback(v => {
      return setValue(oldValue => ({
        ...oldValue,
        ...(typeof v === 'function' ? v(oldValue) : v)
      }));
    }, []),
    state: value
  };
};

Таким образом, вы можете эмулировать поведение «классического» * ​​1017 *.

0 голосов
/ 26 марта 2019

Я пытался решить ее с помощью хука useEffect(), но это не совсем решило мою проблему.Это отчасти сработало, но я в итоге обнаружил, что это слишком сложно для такой простой задачи, как эта, и я также не был уверен, сколько раз выполнялась моя функция и выполнялась ли она после изменения состоянияof not.

В документах на useEffect() упоминаются некоторые варианты использования для ловушки эффекта, и ни один из них не является тем использованием, которое я пытался сделать.

useEffect API reference

Использование ловушки эффектов

Я полностью избавился от ловушки useEffect() и использовал функциональную форму функции setState((prevState) => {...}), которая гарантирует, чтоВы получите текущую версию своего состояния, когда будете использовать ее таким образом.Таким образом, последовательность кода стала следующей:

  // ==========================================================================
  // FUNCTION TO HANDLE ON SUBMIT
  // ==========================================================================

  function onSubmit(event){
    event.preventDefault();
    touchAllInputsValidateAndSubmit();
    return;
  }
  // ==========================================================================
  // FUNCTION TO TOUCH ALL INPUTS WHEN BEGIN SUBMITING
  // ==========================================================================

  function touchAllInputsValidateAndSubmit() {

    let auxInputs = {};
    const shouldSubmit = true;

    setInputs((prevState) => {

      // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
      for (let inputName in prevState) {
        auxInputs = Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
      }

      // TOUCH ALL INPUTS
      for (let inputName in auxInputs) {
        auxInputs[inputName].touched = true;
      }

      return({
        ...auxInputs
      });

    });

    validateAllFields(shouldSubmit);

  }
  // ==========================================================================
  // FUNCTION TO VALIDATE ALL INPUT FIELDS
  // ==========================================================================

  function validateAllFields(shouldSubmit = false) {

    // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
    let auxInputs = {};

    setInputs((prevState) => {

      // CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
      for (let inputName in prevState) {
        auxInputs =
          Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
      }

      // ... all the validation code goes here

      return auxInputs; // RETURNS THE UPDATED STATE

    }); // END OF SETINPUTS

    if (shouldSubmit) {
      checkValidationAndSubmit();
    }

  }

Из объявления validationAllFields() видно, что я выполняю весь свой код для этой функции внутри вызова setInputs( (prevState) => {...}) и чтоудостоверяется, что я буду работать с обновленной текущей версией моего состояния inputs, то есть: я уверен, что все входные данные были затронуты touchAllInputsValidateAndSubmit(), потому что я нахожусь внутри setInputs() с функциональным аргументомform.

  // ==========================================================================
  // FUNCTION TO CHECK VALIDATION BEFORE CALLING SUBMITACTION
  // ==========================================================================

  function checkValidationAndSubmit() {

    let valid = true;

    // THIS IS JUST TO MAKE SURE IT GETS THE MOST RECENT STATE VERSION
    setInputs((prevState) => {

      for (let inputName in prevState) {
        if (inputs[inputName].valid === false) {
          valid = false;
        }
      }
      if (valid) {
        props.submitAction(prevState);
      }

      return prevState;

    });
  }

Обратите внимание, что я использую тот же шаблон setState() с функциональным вызовом аргумента внутри функции checkValidationAndSubmit().Там я также должен убедиться, что я получаю текущее, проверенное состояние, прежде чем я смогу отправить.

Пока это работает без проблем.

...