'Updating state twice in a complex react logic

So, I have a component like this which has quite complex logic in updateCT which I have simpler for the question

import { useState } from "react";
const App = () => {
  const setFinalPerson = (key,value) => {
    let updatedData = key
      ? {
          [key]: value
        }
      : value;
    setPerson((person) => ({
      ...person,
      ...updatedData
    }));
  };

  const updateCT = (key, value) => {
    const { info } = person;
    const newInfo = {
      ...info,
      [key]: value
    };
    setFinalPerson('info', newInfo);
  };

  const onClick = () => {
    updateCT("age", "23");
    updateCT("name", "Henry");
  };

  const [person, setPerson] = useState({
    info: {
      name: "Max",
      age: "22"
    },
    bank: {
      account: "22345333455",
      balance: "7000"
    }
  });
  return (
    <div className="App">
      <h1>{`Name ${person.info.name}`}</h1>
      <h1>{`Age ${person.info.age}`}</h1>
      <h1>{`Account Number ${person.bank.account}`}</h1>
      <h1>{`Balance ${person.bank.balance}`}</h1>

      <button onClick={onClick}>Click</button>
    </div>
  );
};

export default App;

So, when i click on the button, i want to change age and name of the person in info. I know that first it is updating state for age, but when it updates state for name, it gets the older state with old value of age. Therefore, ultimately the age is not getting updated.

According to React Docs, I have to use functional way to update state, but the state object is too complex in real and I just cant use spread operators to that much nesting of object.

Is there any way, I can solve this problem?



Solution 1:[1]

If your true state is actually much more complex than you are showing, a potential solution is the useReducer hook instead. But in this case I fail to see why you would need that, you are essentially wanting to update name and age on the info key at the same time?

but the state object is too complex in real and I just cant use spread operators to that much nesting of object.

There's not really a limit to how much nesting you can have. Perhaps you can re-architect the data structure but it is pretty typical to use a good amount of Object spread to redefine state values. In conclusion you want all of your state changing to happen one time otherwise render will run when you change the state so you will re-overwrite it with the past info and you'll never get to change state twice.

Without seeing your true usecase I fail to see why useReducer would be necessary, but that's an option to consider and useEffect is also a possibility. You can set useEffect to change some state if that's dependent on a different state change. Such as: when the user's name changes, find their age:

const nameByAgeLookup = {
john: 23,
jack: 41,
jill: 12
}

const [name, setName] = useState('')
const [age, setAge] = useState(Infinity)

useEffect(() => {
setAge(nameByAgeLookup[name])
}, [name])

const { useState } = React;
const App = () => {
  const [person, setPerson] = useState({
    info: {
      name: "Max",
      age: "22"
    },
    bank: {
      account: "22345333455",
      balance: "7000"
    }
  });

  const setFinalPerson = (key,value) => {
    let updatedData = key
      ? {
          [key]: value
        }
      : value;
    setPerson((person) => ({
      ...person,
      ...updatedData
    }));
  };

  const updateCT = (newInfo) => {
    const { info } = person;
    const merged = {
      ...info,
      ...newInfo
    };
    setFinalPerson('info', merged);
  };

  const onClick = () => {
    updateCT({age: "23", name: "Henry" });
  };


  return (
    <div className="App">
      <h1>{`Name ${person.info.name}`}</h1>
      <h1>{`Age ${person.info.age}`}</h1>
      <h1>{`Account Number ${person.bank.account}`}</h1>
      <h1>{`Balance ${person.bank.balance}`}</h1>

      <button onClick={onClick}>Click</button>
    </div>
  );
};

ReactDOM.render(
<App />,
window.root
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id='root'></div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1