当前位置: 首页 > 知识库问答 >
问题:

如何在 React 中转义使用状态钩子的无限循环?

闻人英韶
2023-03-14

我是一个反应和学习钩子的新手,但是我不能在第二次执行时从使用状态中逃脱。

我的调用程序函数正在使用参数调用另一个名为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变成了true。

我已经尝试了使用效果挂钩的东西,但没有工作:

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={"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={"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={"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={"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={"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={"inline-form-item"}>
                            <Button type="primary" htmlType="submit">
                                {t.trans.queryPanel.search}
                            </Button>
                        </Form.Item>
                    </Col>
                </Row>
            </Form>

        </div>
    );
}

共有2个答案

胡安怡
2023-03-14
匿名用户

我相信一种方法是这样做:

useEffect(() => {
        if (props.text.length > 0 && props.text[0] !== defaultValue) {
            setDefaultValue(props.text[0]);
        }
    }, [defaultValue]);

通过这样做,您将只在< code>defaultValue与< code>props.text[0]不同时更新它

万俟皓
2023-03-14

创建无限循环是因为React将重新渲染您的

在您的组件正文中,如果...else 语句坐着而不用钩子包裹它:

if (props.text.length > 0) {
    setDefaultDbValue(props.text[0]);
} else {
    let defaultDropDownOption:string = "No Db Available";
    setDefaultDbValue(defaultDropDownOption);
}

因此,无论哪种方式,您都将执行setDefaultDbValue(),这将更新状态并触发重新渲染。

为了防止这种循环,您可以用useEffect()包装这个片段,并使用文本。长度作为依赖项:

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作为依赖项传递给use效果钩子。还是我误解了你的意图?

撇开这一点不谈:设置默认值的更好方法是在use State(

编辑之后,很明显问题在于Ant如何使用prop defaultValue。这个属性不是反应性的,所以在渲染完< code>Select之后更新它没有任何效果。

因此,您可以在呈现面板之前等待请求解决,或者:

如果要选择文本道具的第一个元素,加载后,必须相应地设置“选择的值。请注意,如果将值绑定到状态变量,则必须通过 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"/>

 类似资料:
  • 提前谢了。我有一个状态数组,如下所示。 我需要添加一个项目到状态数组,我发现我们不需要做状态突变。如何使用PrevState设置状态。 如何调用set State以追加此状态数组。 像这样的东西?

  • 在React的官方文件中提到- 如果您熟悉React类生命周期方法,那么可以将useEffect钩子看作componentDidMount、componentDidUpdate和componentWillUnmount的组合。 我的问题是--我们如何在钩子中使用生命周期方法?

  • 问题内容: 我正在尝试新的React Hooks,并有一个带有计数器的Clock组件,该计数器应该每秒增加一次。但是,该值不会增加到超过一。 问题答案: 原因是传递给的闭包中的回调仅访问第一个渲染器中的变量,而无法访问后续渲染器中的新值,因为第二次未调用。 回调中的值始终为0 。 就像您熟悉的状态挂钩一样,状态挂钩有两种形式:一种是处于更新状态的状态,另一种是将当前状态传入的回调形式。您应该使用第

  • 我正在尝试新的React Hooks的API,它似乎一直在无限循环中运行!我只希望中的回调运行一次。下面是我的代码供参考:

  • 问题内容: 因此,我有一个数据数组,并且正在使用该数据生成组件列表。我想在每个生成的元素上都有一个引用来计算高度。我知道如何使用Class组件执行此操作,但是我想使用React Hooks进行操作。 这是一个说明我要做什么的示例: 问题答案: 不确定我是否完全理解您的意图,但我认为您需要这样的东西:

  • 我想用React useState钩子设置多个状态值,这样我就可以用useEffect钩子分别更新它们。我有以下代码: 但是当我用React DevTools检查应用程序时,我会看到钩子的多个“状态”值,而不是“订单”、“支付”、“提交”等。