useEffect & map 超过了最大更新深度

Posted

技术标签:

【中文标题】useEffect & map 超过了最大更新深度【英文标题】:Maximum update depth exceeded with useEffect & map 【发布时间】:2021-06-30 21:04:55 【问题描述】:

我在尝试设置表单错误对象时遇到了这个问题。基本上,我想在每个输入字段下方显示错误。作为回应,我得到了一个对象数组,如何设置我的错误对象?

错误 - 超出最大更新深度。当组件在 useEffect 中调用 setState 时,可能会发生这种情况,但 useEffect 要么没有依赖数组,要么每次渲染时其中一个依赖项都发生了变化。

import axios from "axios";
import React,  useState, useEffect, useCallback  from "react";
import  Link  from "react-router-dom";
import  useDispatch, useSelector  from "react-redux";
import  register  from "../actions/userActions";
const Register = () => 
    const [countriesList, setCountriesList] = useState("");
    const [userRegistration, setUserRegistration] = useState(
        firstName: "",
        lastName: "",
        email: "",
        password: "",
        fullAddress: "",
        city: "",
        zipCode: "",
        country: "",
        phone: "",
        terms: true,
    );
    const [userRegistrationError, setUserRegistrationError] = useState(
        firstNameError: "",
        lastNameError: "",
        emailError: "",
        passwordError: "",
        fullAddressError: "",
        cityError: "",
        zipCodeError: "",
        countryError: "",
        phoneError: "",
        termsError: "",
    );
    const dispatch = useDispatch();

    const userRegister = useSelector((state) => state.userRegister);
    const  loading, errors, success  = userRegister;

    useEffect(() => 
        const countries = async () => 
            try 
                const  data  = await axios.get(
                    `https://restcountries.eu/rest/v2/all`
                );
                setCountriesList(data);
             catch (err) 
                console.error(err);
            
        ;
        countries();
    , []);

    useEffect(() => 
        const handleErrors = (errors) => 
            errors.map((error) => 
                if (error.param === "firstname") 
                    setUserRegistrationError(
                        ...userRegistrationError,
                        firstNameError: error.msg,
                    );
                
                if (error.param === "email") 
                    setUserRegistrationError(
                        ...userRegistrationError,
                        emailError: error.msg,
                    );
                
                return null;
            );
        ;
        if (errors) 
            handleErrors(errors);
        
    , [errors, setUserRegistrationError]);

    const handleChange = (e) => 
        const name = e.target.name;
        const value = e.target.value;
        setUserRegistration( ...userRegistration, [name]: value );
    ;
    const handleChkChange = (e) => 
        const checked = e.target.checked;
        console.log(checked);
        setUserRegistration( ...userRegistration, terms: checked );
    ;

    const handleSubmit = (e) => 
        e.preventDefault();
        try 
            dispatch(register());
         catch (error) 
            console.error(error);
        
    ;
    return (
        <div className="form_container">
            <form action="" onSubmit=handleSubmit>
                <div className="row no-gutters">
                    <div className="col-6 pr-1">
                        <div className="form-group">
                            <div className="form-group">
                                <input
                                    type="text"
                                    name="firstName"
                                    className="form-control"
                                    placeholder="First Name*"
                                    value=userRegistration.firstName
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.firstNameError &&
                                        userRegistrationError.firstNameError
                                </p>
                            </div>
                        </div>
                    </div>
                    <div className="col-6 pr-1">
                        <div className="form-group">
                            <input
                                type="text"
                                className="form-control"
                                name="lastName"
                                placeholder="Last Name*"
                                value=userRegistration.lastName
                                onChange=handleChange
                            />
                            <p className="form-vald-error">
                                userRegistrationError.lastNameError &&
                                    userRegistrationError.lastNameError
                            </p>
                        </div>
                    </div>
                </div>
                <hr />
                <div className="private box">
                    <div className="row no-gutters">
                        <div className="col-6 pr-1">
                            <div className="form-group">
                                <input
                                    type="email"
                                    className="form-control"
                                    name="email"
                                    id="email_2"
                                    placeholder="Email*"
                                    value=userRegistration.email
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.emailError &&
                                        userRegistrationError.emailError
                                </p>
                            </div>
                        </div>
                        <div className="col-6 pl-1">
                            <div className="form-group">
                                <input
                                    type="password"
                                    className="form-control"
                                    name="password"
                                    id="password_in_2"
                                    placeholder="Password*"
                                    value=userRegistration.password
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.passwordError &&
                                        userRegistrationError.passwordError
                                </p>
                            </div>
                        </div>
                        <div className="col-12">
                            <div className="form-group">
                                <input
                                    type="text"
                                    name="fullAddress"
                                    className="form-control"
                                    placeholder="Full Address*"
                                    value=userRegistration.fullAddress
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.fullAddressError &&
                                        userRegistrationError.fullAddressError
                                </p>
                            </div>
                        </div>
                    </div>
                    /* /row */
                    <div className="row no-gutters">
                        <div className="col-6 pr-1">
                            <div className="form-group">
                                <input
                                    type="text"
                                    className="form-control"
                                    placeholder="City*"
                                    name="city"
                                    value=userRegistration.city
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.cityError &&
                                        userRegistrationError.cityError
                                </p>
                            </div>
                        </div>
                        <div className="col-6 pl-1">
                            <div className="form-group">
                                <input
                                    type="text"
                                    className="form-control"
                                    placeholder="Postal Code*"
                                    name="zipCode"
                                    value=userRegistration.zipCode
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.zipCodeError &&
                                        userRegistrationError.zipCodeError
                                </p>
                            </div>
                        </div>
                    </div>
                    /* /row */
                    <div className="row no-gutters">
                        <div className="col-6 pr-1">
                            <div className="form-group">
                                <div className="custom-select-form">
                                    <select
                                        className="wide add_bottom_10 form-control"
                                        name="country"
                                        id="country"
                                        value=userRegistration.country
                                        onChange=handleChange
                                    >
                                        <option>Country*</option>
                                        countriesList &&
                                            countriesList.map((country) => (
                                                <option
                                                    key=country.alpha2Code
                                                    value=country.alpha2Code
                                                >
                                                    country.name
                                                </option>
                                            ))
                                    </select>
                                    <p className="form-vald-error">
                                        userRegistrationError.countryError &&
                                            userRegistrationError.countryError
                                    </p>
                                </div>
                            </div>
                        </div>
                        <div className="col-6 pl-1">
                            <div className="form-group">
                                <input
                                    type="text"
                                    className="form-control"
                                    placeholder="Telephone *"
                                    name="phone"
                                    value=userRegistration.phone
                                    onChange=handleChange
                                />
                                <p className="form-vald-error">
                                    userRegistrationError.phoneError &&
                                        userRegistrationError.phoneError
                                </p>
                            </div>
                        </div>
                    </div>
                    /* /row */
                </div>
                <hr />
                <div className="form-group">
                    <label className="container_check">
                        Accept <Link to="#0">Terms and conditions</Link>
                        <input
                            type="checkbox"
                            name="terms"
                            checked=userRegistration.terms
                            onChange=handleChkChange
                        />
                        <span className="checkmark" />
                        <p className="form-vald-error">
                            userRegistrationError.termsError &&
                                userRegistrationError.termsError
                        </p>
                    </label>
                </div>
                <div className="text-center">
                    <input
                        type="submit"
                        defaultValue="Register"
                        className="btn_1 full-width"
                    />
                </div>
            </form>
        </div>
    );
;

export default Register;

【问题讨论】:

我们需要看看 JSX,它会对我们有所帮助。 jsx 没有问题,只有第二个 useEffect 导致了这个问题,但我不知道如何解决它 【参考方案1】:

你的第二个useEffect有问题,第一次更新你的状态userRegistrationError,组件重新渲染并重新执行了useEffect,因为依赖userRegistrationError 已更改,并且该过程再次重复,因为每次渲染都会更新状态。

 useEffect(() => 
        const handleErrors = (errors) => 
            errors.map((error) => 
                if (error.param === "firstname") 
                    setUserRegistrationError(
                        ...userRegistrationError,
                        firstNameError: error.msg,
                    );
                
                if (error.param === "email") 
                    setUserRegistrationError(
                        ...userRegistrationError,
                        emailError: error.msg,
                    );
                
                return null;
            );
        ;
        if (errors) 
            handleErrors(errors);
        
    , [errors, setUserRegistrationError ]); //replace userRegistrationError by setUserRegistrationError

【讨论】:

我得到了那部分,但我是新手,不知道如何解决它。我尝试在外面定义handleErrors,但它不起作用。你能帮忙吗? 看看我的答案 用 [errors, setUserRegistrationError] 替换 [errors, userRegistrationError] 我没有看到,但它不起作用 eslint 正在抱怨。我正在添加整个代码 忽略 eslint 并使用 [errors, setUserRegistrationError] 测试您的代码。 我已经尝试过您的编辑,但它现在无法正常运行,它没有无限运行,而是设置了最后一个错误值,即电子邮件而不是名字和电子邮件【参考方案2】:

您的效果取决于userRegistrationError,它是一个基于引用的对象。每次useEffect运行,setUserRegistrationError 创建一个新的对象引用,这会导致无限循环,因为引用不会与前一个相同。

避免此问题并保留正确引用的一种方法是将回调函数传递给setUserRegistrationError,而不是传递一个值。这样userRegistrationError 就不再是依赖项了,而是作为函数的参数:

useEffect(() => 
    const handleErrors = (errors) => 
        errors.forEach((error) => 
            if (error.param === "firstName") 
                // here you pass a callback function instead, and userRegistrationError is no longer a dependency
                // and returns the next state as expected
                setUserRegistrationError(userRegistrationError => (
                    ...userRegistrationError,
                    firstNameError: error.msg,
                ));
            
            if (error.param === "email") 
                setUserRegistrationError(userRegistrationError => (
                    ...userRegistrationError,
                    emailError: error.msg,
                ));
            
        );
    ;
    if (errors) 
        handleErrors(errors);
    
, [errors, setUserRegistrationError]);

【讨论】:

我已经尝试过您的编辑,但它现在无法正常运行,它没有无限运行,而是设置了最后一个错误值,即电子邮件而不是名字和电子邮件 我认为问题是名字应该是驼峰式firstName 给定您的输入,对吗? fwiw 你应该使用forEach 而不是map 当你只是迭代一个数组时 参数没有问题看起来像在 set 函数中我必须这样写 (userRegistrationError) => ( ...userRegistrationError, firstNameError: error.msg, ) @NeerajMishra 很好,但这正是我首先发布答案的方式...... 我在回答时告诉过要传递一个函数(它也在代码中),我相信你可能没有看到

以上是关于useEffect & map 超过了最大更新深度的主要内容,如果未能解决你的问题,请参考以下文章

使用 useEffect 钩子在反应功能组件中未使用 .map() 显示的元素

乐观渲染 & useEffect

Tridion CMS & Oracle: ORA-01000: 超过最大打开游标

当 useRef 元素的高度大于零时的事件

使用用户输入更新 useEffect api 调用 url

最大似然估计和最大后验概率估计(MLE&MAP)