簡體   English   中英

如何在 React 中逃脫 useState 鈎子的無限循環?

[英]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]);

通過這樣做,您將僅在defaultValueprops.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道具的第一個元素,一旦它被加載,你必須相應地設置Selectvalue 請注意,如果將值綁定到 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM