如何确保使用useState()挂钩的React状态已更新?

时间:2019-03-25 19:12:15

标签: javascript reactjs react-hooks

我有一个名为<BasicForm>的类组件,用于与之建立表单。它处理验证以及所有格式state。它通过React上下文为输入(呈现为onChange的{​​{1}})提供所有必要的功能(onSubmitchildren等)。

它按预期工作。问题在于,现在我将其转换为使用React Hooks,在尝试复制以下是类时的行为时,我感到疑惑:

BasicForm

当用户单击“提交”按钮时,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()
  

警告:通过useState()和useReducer()挂钩进行状态更新   不支持第二个回调参数。产生副作用   渲染后,使用useEffect()在组件主体中声明它。

因此,根据上面的错误消息,我正在尝试使用const [inputs, setInputs] = useState({}); ... some other code ... setInputs(auxInputs, () => console.log('Inputs updated!')); 钩子执行此操作。但这使我有些困惑,因为据我所知,useEffect()不是基于状态更新,而是基于渲染执行。它在每次渲染后执行。而且我知道React可以在重新渲染之前对一些状态更新进行排队,所以我觉得我无法完全控制我的useEffect()钩子何时执行,就像我在使用类和{ {1}}第二个回调参数。

到目前为止,我得到的(似乎正在起作用):

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状态已经100%确定为“假”?

我可以依靠React在下一次渲染之前执行每个排队状态更新吗?这样可以保证吗?

2 个答案:

答案 0 :(得分:2)

我最近遇到了类似的问题(问题here),看来您想出的是一个不错的方法。

您可以向useEffect()添加一个应执行所需操作的arg:

例如

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
  };
};

通过这种方式,您可以模拟“经典” setState()的行为。

答案 1 :(得分:0)

我尝试使用useEffect()钩子来解决它,但是并不能完全解决我的问题。这种方法行得通,但是我最终发现对于像这样的简单任务而言,它有点太复杂了,而且我对函数执行了多少次以及状态更改后是否执行了函数也不太确定不是。

useEffect()上的文档中提到了一些有关效果挂钩的用例,但这些用例都不是我试图做的。

useEffect API reference

Using the effect hook

我完全摆脱了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) => {...})的调用中执行该函数的所有代码,并确保我将使用{ {1}}的状态,即:我确定所有输入都已被inputs触摸过,因为我位于touchAllInputsValidateAndSubmit()内,具有函数参数形式。

setInputs()

看到我在 // ========================================================================== // 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()的相同模式。在此之前,我还需要确保已获得当前已验证的状态,然后才能提交。

到目前为止,该方法没有任何问题。