0

In the past, i was writing reactjs code like this:

  class App extends Component {
    state = { var1:null, var2:null, var3:null };
    
    myfunction1 = async () => {
       ...
       this.setState({ var1:10, var2:20, var3:30 }, this.myfunction2); 
    }

    myfunction2 = async () => {
       ...
    };

    render() {
       console.log("render");
       return (
          <div className="App">
            { this.state.var1 }
            { this.state.var2 }
            { this.state.var3 }
          </div>
       );
    }

Now, i am working this way:

const App = () => {
  const [var1, setVar1] = useState(null);
  const [var2, setVar2] = useState(null);
  const [var3, setVar3] = useState(null);

  async function init() {
     setVar1(10);
     setVar2(20);
     setVar3(30);
     function2();
  }

  useEffect(() => { init(); }, []);

  async function function2() {

  }

  console.log("render");
  return (
          <div className="App">
            { var1 }
            { var2 }
            { var3 }
          </div>
       );

Here are my problems in the second way:

  • Is there a way to make setVar1, setVar2 and setVar3 in a same call ? The problem is the page will be refreshed 3 times. In the first refresh, var1 will equal to 10, but var2 and var3 will be null. It can cause some problems...

  • How can i be sure function2 will be called AFTER var1, var2 and var3 will be set ? In the first approach there is a callback function which is called only when states are set. How can i do the same thing in the second approach ?

Thanks

Bob5421
  • 7,757
  • 14
  • 81
  • 175

3 Answers3

2

Notice that your two examples are not equivalent, you need to have a single state object so they will be.

Is there a way to make setVar1, setVar2 and setVar3 in a same call ?

You are actually asking how to batch state changes, React does not batch promise calls like you have (async).

Either change the promise call to normal call, or try a common solution like having a single state object.

useState({ var1: null, var2: null, var3: null })

How can I be sure function2 will be called AFTER init

You are using async calls, so just call function2 after the first call resolved. Or you can have a boolean reference to indicate you are after init call, similar logic on having useEffect stop running after mount.

Full example:

const App = () => {
  const [state, setState] = useState({ var1: null, var2: null, var3: null });

  // or
  const isInitCalled = useRef(false);

  function function2() {
    console.log("after");
  }

  useEffect(async () => {
    async function init() {
      setVar1({ var1: 10, var2: 20, var3: 30 });
      function2();
    }

    await init();
    function2();
  }, []);

  // Or
  useEffect(() => {
    init();
    isInitCalled.current = true;
  }, []);

  useEffect(() => {
    if (isInitCalled.current) {
      func2();
    }
  }, [state]);

  return (
    <div className="App">
      {var1}
      {var2}
      {var3}
    </div>
  );
};
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
0

You can use an array or an object instead of primitive values:

const App = () => {
  const [vars, setVars] = useState([null, null, null]);

  async function init() {
     setVars([10, 20, 30]);
     function2();
  }

  useEffect(() => { init(); }, []);

  async function function2() {

  }

  console.log("render");
  return (
          <div className="App">
            { vars[0] }
            { vars[1] }
            { vars[2] }
          </div>
       );

Of course if your actual 'vars' isn't numbered, you might prefer an object with properties instead of an array.

Evert
  • 93,428
  • 18
  • 118
  • 189
0

Is there a way to make setVar1, setVar2 and setVar3 in a same call ? The problem is the page will be refreshed 3 times. In the first refresh, var1 will equal to 10, but var2 and var3 will be null. It can cause some problems...

First, i just want to point out that it's not necessarily going to render multiple times. If the code execution begins within react (eg, a useEffect or a synthetic event), then react will batch up multiple state changes and only do one rerender. But it's true that in some cases, react didn't start the execution, so execution won't return to react, and so react has no choice but to rerender immediately. For example, this would happen if you're setting state in a setTimeout callback, or after awaiting a promise.

If you need to set state multiple times with only one rerender, you can use unstable_batchedUpdates.

import { unstable_batchedUpdates } from 'react';

// ...
async function init() {
   unstable_batchedUpdates(() => {
     setVar1(10);
     setVar2(20);
     setVar3(30);    
   })

   function2();
}

The term "unstable" is the react team's way of saying that this api may change in the next major version of react. But it's perfectly safe to use, as long you pay attention to that when upgrading your react version.

How can i be sure function2 will be called AFTER var1, var2 and var3 will be set ? In the first approach there is a callback function which is called only when states are set. How can i do the same thing in the second approach ?

Setting state in function components doesn't come with a way to know when the component rerenders. And that's probably because it wouldn't do any good. There's no this that's being mutated, so no matter how much time passes, function2 will not see the new values. It has in its closure the values from the current render, and cannot access the next render.

Instead, the fix is most likely to pass the new values into function2. For example, if it needs to know that var1 is becoming 10, you'll need to pass that 10 in.

async function init() {
   const newVar1 = 10;
   unstable_batchedUpdates(() => {
     setVar1(newVar1);
     setVar2(20);
     setVar3(30);    
   })

   function2(newVar1);
}

async function function2(newVar1) {

}
Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98