Neeraj Mishra
Neeraj Mishra

Reputation: 13

Maximum update depth exceeded with useEffect & map

I am facing this when I am trying to set form error object. Basically, I want to show the errors below each input field. In response, I am getting an array of objects how do I set to my error object?

Error - Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

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;

Upvotes: 1

Views: 1174

Answers (2)

buzatto
buzatto

Reputation: 10382

your effect depends on userRegistrationError which is an object, reference based. Each time useEffect runs,setUserRegistrationError creates a new object reference, which leads to an infinite loop since references won't be the same as the previous one.

One approach to avoid this issue and keep the right references, is to pass a callback function to setUserRegistrationError instead than a value. This way userRegistrationError is no longer a dependency, it will be an argument to your function instead:

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

Upvotes: 1

Soufiane Boutahlil
Soufiane Boutahlil

Reputation: 2604

You have a problem with the second useEffect, the first time you update your state userRegistrationError, the component re-rendered and re-executed the useeffect because the dependency userRegistrationError has changed and the process gets repeated again because the state gets updated every render.

 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

Upvotes: 0

Related Questions