![](/img/trans.png)
[英]Making sure that useState() hook has been updated by useEffect hook?
[英]How to make sure a React state using useState() hook has been updated?
我有一個名為<BasicForm>
的類組件,用於構建表單。 它處理驗證和所有表單state
。 它提供了所有必要的功能( onChange
, onSubmit
等)到輸入端(呈現為children
的BasicForm
)經由陣營上下文。
它按預期工作。 問題在於,現在我將其轉換為使用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()
僅在狀態更新(涉及所有字段的狀態validateAllField()
才被調用。
但是,當我嘗試將第二個回調參數與狀態掛鈎useState()
,出現以下錯誤:
const [inputs, setInputs] = useState({});
... some other code ...
setInputs(auxInputs, () => console.log('Inputs updated!'));
警告:useState()和useReducer()掛鈎的狀態更新不支持第二個回調參數。 要在渲染后執行副作用,請使用useEffect()在組件主體中聲明它。
因此,根據上面的錯誤消息,我正在嘗試使用useEffect()
掛鈎進行此操作。 但這使我有些困惑,因為據我所知, 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在下一次渲染之前執行每個排隊狀態更新嗎? 這樣可以保證嗎?
最近我遇到了類似的問題( 這里是 SO問題),看來您想出的是一個不錯的方法。
您可以在useEffect()
中添加一個arg,該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()
的行為。
我試圖使用useEffect()
鈎子解決它,但是並不能完全解決我的問題。 這種方法行得通,但是我最終發現對於像這樣的簡單任務而言,它有點太復雜了,而且我對函數執行了多少次以及狀態更改后是否執行了函數也不太確定不。
useEffect()
上的文檔提到了效果掛鈎的一些用例,而這些都不是我試圖做的用途。
我完全擺脫了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()
。
// ==========================================================================
// 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;
});
}
看到我在checkValidationAndSubmit()
函數內部使用了帶函數參數調用的setState()
模式。 在此之前,我還需要確保已獲得當前已驗證的狀態,然后才能提交。
到目前為止,這沒有任何問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.