Home > other >  Two independent state piece causing infinite loop - useEffect
Two independent state piece causing infinite loop - useEffect

Time:07-25

I can't wrap my head around the problem I'm experiencing Basically I submit the form and it checks whether or not there are empty values. I then paint the input border either red or green. However, I need to repaint the border all the time, meaning that if user enters a value, the border should turn green (hence the useEffect). I have 2 pieces of state here. One keeps track of validation error indexes (for value === '') The other piece is the createForm state (form fields) itself. I then send down the indexes via props. I've been trying to observe the origin of this infinite loop for hours now and cursing react and myself. It's getting dark and grim.

NOTE: infinite loop occurs not on initial render, but when form is submitted with empty values. The infinite loop DOES NOT occur if there is no empty field on form submit.

I'm willing to share additional info on demand.

      const [createForm, setCreateForm] = React.useState(() => createFormFields);
      const [validationErrorIndexes, setValidationErrorIndexes] = React.useState([]);

//Function that is being triggered in useEffect - to recalculate validaiton error indexes and resets the indexes.
  const validateFormFields = () => {

    const newIndexes = [];

    createForm.forEach((field, i) => {
      if (!field.value) {
        newIndexes.push(i);
      }
    })

    setValidationErrorIndexes(newIndexes);
  }

//(infinite loop occurs here).
  React.useEffect(() => {

    if (validationErrorIndexes.length) {
      validateFormFields();
      return;
    }

  }, [Object.values(createForm)]);


//Function form submit.
  const handleCreateSubmit = (e) => {
    e.preventDefault();

    if (createForm.every(formField => Boolean(formField.value))) {
      console.log(createForm)
      // TODO: dispatch -> POST/createUser...
    } else {
      validateFormFields();
    }
  }



//I then pass down validationErrorIndexes via props and add error and success classes conditionally to paint the border.
 {createForm && createForm.length && createForm.map((formEl, i) => {

                if (formEl.type === 'select') {
                  return (
                    <Select
                      className={`create-select ${(validationErrorIndexes.length && validationErrorIndexes.includes(i)) && 'error'}`}
                      styles={customStyles}
                      placeholder={formEl.label}
                      key={i}
                      value={formEl.value}
                      onChange={(selectedOption) => handleOptionChange(selectedOption, i)}
                      options={formEl.options}
                    />
                  )
                }


                return (
                  <CustomInput key={i} {...{ label: formEl.label, type: formEl.type, value: formEl.value, formState: createForm, formStateSetter: setCreateForm, i, validationErrorIndexes }} />
                )
              })}




CodePudding user response:

Ok, so here is what is happening:

  1. Initial render - validationErrorIndexes is empty, bugged useEffect does not hit if and passes.

  2. You click submit with 1 empty field - submit calls validateFormFields, it calculates, setValidationErrorIndexes is set, now its length is non zero and if in bugged useEffect will be hit. And here we got a problem...

The problem: on each rerender of your component Object.values(createForm) which are in your dependency array are evaluated and returning new [array]. Every single rerender, not the old one with same data, the new one with same data. And asuming the if guard is gone now due to length is non 0 - validateFormFields is called again. Which does its evaluations, and setting new data with setValidationErrorIndexes. Which causes rerendering. And Object.values(createForm) returns a new array again. So welp.

So basically, 2 solutions.

One is obvious and just replace [Object.values(createForm)] with just [createForm]. (why do you need Object.values here btw?)

Second one - well if it a must to have [Object.values(createForm)].

  const validateFormFields = () => {
    const newIndexes = [];
    console.log(createForm);
    createForm.forEach((field, i) => {
      if (!field.value) {
        newIndexes.push(i);
      }
    });
    console.log(newIndexes);
    setValidationErrorIndexes((oldIndexes) => {
      // sorry, just an idea. Compare old and new one and return old if equals.
      if (JSON.stringify(oldIndexes) === JSON.stringify(newIndexes)) {
        return oldIndexes; // will not cause rerender.
      }

      return newIndexes;
    });
  };
  • Related