skip to Main Content

I have a form with many input elements and a button which goes on next page. Before moving forward it validates all the inputs in such a way that if any it is empty then the corresponding label should be red. For that I made a class text-red in css and if any value is empty it sets it to that class.
I decided to store that value in object itself for every key.

I have an object like this (Couldn’t post the actual code)

const [data,setData] = useState({
            key1:{value:'',valueTextClass:'', formula:'', formulaInputClass:''},
            key2:{value:'',valueTextClass:'', formula:'', formulaInputClass:''},
            key3:{value:'',valueTextClass:'', formula:'', formulaInputClass:''}
           })

When I click on the button, it calls a validation function and it validates every field one by one.
This is the block of code I have written.

const validate = (key) => {
    Object.keys(data).forEach(key => {
            if(data[key].value == '')
                 setData(prev => (...prev, [key]:({...data[key], valueTextClass:'text-red' })));
            if(data[key].formula == '')
                 setData(prev => (...prev, [key]:({...data[key], formulaInputClass:'text-red' })))
            })
}

When both values are empty then only the formula label sets to red, value label remains the same.
I have tried a lot to figure out but I ended with nothing.

2

Answers


  1. The problem is related to the multiple calls to setData. Set state actions are async and therefore you have to call it one time to solve the issue.

    const validate = (key) => {
      let newData = { ...data };
      Object.keys(data).forEach((key) => {
        newData = {
          ...newData,
          [key]: {
            ...newData[key],
            valueTextClass: newData?.[key]?.value === '' ? 'text-red' : '',
            formulaInputClass: newData?.[key].formula == '' ? 'text-red' : '',
          },
        };
      });
      setData(newData);
    };
    

    In this way you build a single object that then updates the state correctly in only 1 call.

    Login or Signup to reply.
  2. In order to update all values asynchronously you should wrap the full update functionality in setData and not just each individual statement. Below is a solution similar to yours that does just that.

    const validate = () => {
      setData(prev => {
        // create a new reference to the data so react knows it might have changed
        let newData = { ...prev };
    
        Object.keys(newData).forEach(key => {
          if (newData[key].value === '')
            newData[key] = { ...newData[key], valueTextClass: 'text-red' };
          if (newData[key].formula === '')
            newData[key] = { ...newData[key], formulaInputClass:'text-red' };
        });
      });
    }
    

    Some things to note:

    • The input key to validate is never used so it can be removed.
    • If we don’t shallow copy the data first by newData = { ...prev }, but instead modify prev directly, React won’t know about the changes and we will get bugs.
    • data and prev will likely be the same inside the scope of the update function passed to setData. However, this is not guaranteed and we should only use prev since it is guaranteed to contain the latest changes.

    Below is how I would have solved the issue, with a functional approach and by leveraging Object.entries.

    const validate = () => {
      setData(prev => {
        const updatedEntries =
          Object
            // transform into a list of entries
            .entries(prev)
            // modify each set of entries
            .map(([key, value]) => ([
              key,
              {
                ...value,
                valueTextClass:
                  value.value === '' ? 'text-red' : value.valueTextClass,
                formulaInputClass:
                  value.formula === '' ? 'text-red' : value.formulaInputClass
              }
            ]));
        // transform back into an object
        return Object.fromEntries(updatedEntries);     
      })
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search