繁体   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