[英]How to escape infinite loop of useState hook in React?
我是新來的反應和學習鈎子,但我無法在第二次執行時從 useState 中逃脫。
我的調用者 function 正在調用另一個名為 QueryPanel 的組件,其參數為:
const [availables, setAvailable] = useState<string[]>([]);
useEffect(() => {
context.services.records.getAvailablesList()
.then((resp) => {
if(resp != undefined) {
setAvailables(resp);
}
});
}, []);
return <Panel1 text={availables}></Panel1>;
所以 Panel1 以字符串數組或空數組打開。 我想做的是顯示下拉菜單,如果參數不為空,則將第一項顯示為默認項,或者如果參數列表為空,則將字符串顯示為默認值“無可用選項”
export const QueryPanel = (props: any) => {
const t = useTrans();
const context = useContext(AppContext);
const [form] = Form.useForm<Store>();
const [defaultValue, setDefaultValue] = useState("asd");
const { Option } = Select;
const onFinish = (values: Store) => {
const queryParams: Query = {
system: values.systemType,
startDate: values.startDate,
endDate: values.endDate,
startFrequency: values.startFrequency,
endFrequency: values.endFrequency
};
context.services.records.query(queryParams);
};
const validateOnFormChange = () => {
form.validateFields();
};
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
console.log('Failed:', errorInfo);
};
// useEffect(() => {
//
// if (props.text.length !== 0) {
// setDefaultValue(props.text[0]);
// }
// else {
// setDefaultValue("No option available");
// }
// }, []);
// if (props.text.length > 0) {
// setDefaultDbValue(props.text[0]);
// }
// else {
// let defaultDropDownOption:string = "No Db Available";
// setDefaultDbValue(defaultDropDownOption);
// }
我的 if-else 陷入無限循環,因為每當我將默認值設置為一個值時,它就會再次進入相同的 if 語句。 如何在不使用額外字段的情況下逃脫它? useEffect 在這里被注釋掉了,奇怪的是當我檢查參數prop.text時它在開始時是未定義的,這就是為什么代碼沒有進入 useEffect 中的 if-else 然后在第二次迭代時 prop.text 是真實的.
我用 useEffect 鈎子嘗試了一些東西,但沒有奏效:
useEffect(() => {
if (props.text.length > 0) {
setDefaultValue(props.text[0]);
}
}, [defaultValue]);
但是這次默認數據庫是空的
這是 Panel1 (QueryPanel) 的完整代碼,但有些名稱不同
import { Button, Col, DatePicker, Form, InputNumber, Row, Select } from 'antd';
import { Store } from 'antd/lib/form/interface';
import { ValidateErrorEntity } from 'rc-field-form/lib/interface';
import React, {useContext, useEffect, useState} from 'react';
import { AppContext } from '../../../../shared/Context';
import './QueryPanel.less';
import { Query } from '../../../../rest/BackendApi';
import { SystemType } from '../../../../shared/models/SystemType';
import { useTrans } from "../../../../shared/i18n/i18n";
export const QueryPanel = (props: any) => {
const t = useTrans();
const context = useContext(AppContext);
const [form] = Form.useForm<Store>();
const [defaultDbValue, setDefaultDbValue] = useState("Select Db");
const { Option } = Select;
const onFinish = (values: Store) => {
const queryParams: Query = {
system: values.systemType,
startDate: values.startDate,
endDate: values.endDate,
startFrequency: values.startFrequency,
endFrequency: values.endFrequency
};
context.services.records.query(queryParams);
};
const validateOnFormChange = () => {
form.validateFields();
};
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
console.log('Failed:', errorInfo);
};
// useEffect(() => {
// if (props.text.length !== 0) {
// setDefaultDbValue(props.text[0]);
// }
// else {
// setDefaultDbValue("No db available");
// }
// }, []);
useEffect(() => {
if (!props.text) return;
if (props.text.length > 0) {
setDefaultDbValue(props.text[0]);
}
else setDefaultDbValue("No Db Available");
}, [props.text?.length]);
return (
<div>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed} onValuesChange={validateOnFormChange}>
<Row justify={"space-between"} >
<Col span={4}>
<Form.Item label={t.trans.queryPanel.systemType} name='systemType' className={"tay-inline-form-item"}>
<Select defaultValue={defaultDbValue}>
{props.text.map((element: any) => (
<Option key={element} value={element}>
{element}
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.startDate} name='startDate' className={"tay-inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || !getFieldValue('endDate') || getFieldValue('endDate') >= value) {
return Promise.resolve();
}
return Promise.reject(t.trans.queryPanel.errors.startDate);
},
}),
]}>
<DatePicker format="DD-MM-YYYY" />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.endDate} name='endDate' className={"tay-inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || !getFieldValue('startDate') || getFieldValue('startDate') <= value) {
return Promise.resolve();
}
return Promise.reject(t.trans.queryPanel.errors.endDate);
},
}),
]}>
<DatePicker format="DD-MM-YYYY" />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.startFrequency} name='startFrequency' className={"tay-inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value) return Promise.resolve();
if (value < 0 || value > 200) {
return Promise.reject(t.trans.queryPanel.errors.startFrequency);
} else if (getFieldValue('endFrequency') && getFieldValue('endFrequency') < value) {
return Promise.reject(t.trans.queryPanel.errors.startFrequencyValues);
}
return Promise.resolve();
},
}),
]}>
<InputNumber />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.endFrequency} name='endFrequency' className={"tay-inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value) return Promise.resolve();
if (value < 0 || value > 400) {
return Promise.reject(t.trans.queryPanel.errors.endFrequency);
} else if (getFieldValue('startFrequency') && getFieldValue('startFrequency') > value) {
return Promise.reject(t.trans.queryPanel.errors.endFrequencyValues);
}
return Promise.resolve();
},
}),
]}>
<InputNumber />
</Form.Item>
</Col>
<Col span={2}>
<Form.Item className={"tay-inline-form-item"}>
<Button type="primary" htmlType="submit">
{t.trans.queryPanel.search}
</Button>
</Form.Item>
</Col>
</Row>
</Form>
</div>
);
}
我相信一種方法是:
useEffect(() => {
if (props.text.length > 0 && props.text[0] !== defaultValue) {
setDefaultValue(props.text[0]);
}
}, [defaultValue]);
通過這樣做,您將僅在defaultValue
與props.text[0]
不同時更新它
創建無限循環是因為每當組件的 state 發生更改時,React 都會重新渲染您的<Panel1 />
組件。
在你的組件的主體中,你有這個if... else
語句,沒有將它包裝在一個鈎子中:
if (props.text.length > 0) {
setDefaultDbValue(props.text[0]);
} else {
let defaultDropDownOption:string = "No Db Available";
setDefaultDbValue(defaultDropDownOption);
}
因此,無論哪種方式,您都將執行setDefaultDbValue()
,這將更新 state 並觸發重新渲染。
為了防止這個循環,你可以用useEffect()
包裝這個片段並使用你的text.length
作為依賴項:
useEffect(() => {
if (!props.text) return; // wait until it is defined.
if (props.text.length > 0) setDefaultDbValue(props.text[0]);
else setDefaultDbValue("No Db Available");
}, [props.text?.length]);
無需將defaultValue
作為依賴項傳遞給useEffect
掛鈎。 還是我誤解了你的意圖?
除此之外:設置默認值的更好方法是直接在useState(<DEFAULT_VALUE>)
掛鈎中進行。 就像你做的那樣。 為什么這不是您的選擇?
編輯后,很明顯問題在於Ant如何使用道具defaultValue
。 此道具不是反應性的 - 因此在呈現Select
后更新它沒有效果。
因此,您可以在渲染面板之前等待請求解決,或者:
如果你想 select 你的text
道具的第一個元素,一旦它被加載,你必須相應地設置Select
的value
。 請注意,如果將值綁定到 state 變量,則必須通過onChange
處理程序更新值。
這里有一個例子:
const Select = antd.Select; const values = [ 'one', 'two', 'three', ]; // This is just a dummy representing your `text` prop const lazyValues = new Promise((res) => { setTimeout(() => { res(values); }, 2000); }); const App = () => { const [text, setText] = React.useState([]); const [value, setValue] = React.useState(); React.useEffect(() => { // fake the request lazyValues.then((values) => setText(values)); }, []); // if `text` changes its value, set the Select to its first element React.useEffect(() => { if (text.length > 0 &&;value) setValue(text[0]), }; [text]). return <div> <Select onChange={setValue /* Need to update our value manually */} defaultValue={"Default value visible if there is no value"} value={value}> {text,map((text. i) => <Select.Option key={i} value={text}>{text}</Select.Option>)} </Select> </div> } ReactDOM,render(<App />. document;getElementById('app'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script crossorigin src="https://unpkg.com/antd@4.17.4/dist/antd.min.js"></script> <link rel='stylesheet' type="text/css" href="https://unpkg.com/antd@4.17.4/dist/antd.min.css"></link> <div id="app"/>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.