2

Consider these two usages of useEffect in React:

useEffect(() => {
  setSomeState(complexComputation(someDependency));
}, [someDependency]);

vs

useEffect(() => {
  setTimeout(() => {
    setSomeState(complexComputation(someDependency));
  }, 0);
}, [someDependency]);

They effectively do the same thing, but the technical difference is that the function passed to useEffect in the first case is blocking, whereas in the second case it is asynchronous.

Do these two usages differ in any way from the perspective of the React rendering flow? Does React take care of asynchronous scheduling of effects internally, or should I do that manually for synchronous/costly effects?


To clarify the comments below: I initially made this mistake when asking the question.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • Wrapping the code in a Promise has no effect so if useEffect were to wrap it in another Promise it would still have no effect. – Quentin Dec 09 '20 at 11:52
  • 1
    the question was about the implementation of `useEffect()` and shouldn't be duplicate... – adir abargil Dec 09 '20 at 12:00
  • The question was "Does useEffect internally wrap the function into a Promise anyway, so that wrapping it with yet another Promise has basically no effect at all?". As the duplicate says, and as I repeated in my first comment: Since wrapping it in a Promise has no effect at all anyway, it has no effect here, so if useEffect does wrap it in a promise then it has no effect and if useEffect doesn't wrap it in a promise then it still has no effect. So the answer is that it has no effect. – Quentin Dec 09 '20 at 12:02
  • 1
    @adirabargil — The question is based on a misunderstanding of how promises work, and once that is addressed the implementation of `useEffect` becomes irrelevant to the question. – Quentin Dec 09 '20 at 12:08
  • "Well, if React would try to use the passed-in function in a synchronous way, i.e., await its results internally, it would make a difference." — No, it wouldn't. The component function would continue to render because `useEffect` doesn't return a promise and you aren't awaiting it anyway. – Quentin Dec 09 '20 at 12:11
  • @bluenote10 neither of your two `useEffect` callbacks returns anything that react could use to distinguish the two cases. It cannot make a difference, and it doesn't make a difference. – Bergi Dec 09 '20 at 13:11
  • 1
    @Quentin: I have edited the question, does it make more sense now? – bluenote10 Dec 09 '20 at 13:18
  • It's still not quite clear to me. Are you asking whether `useEffect()` does synchronously call its callback, i.e. during the render call to your component? It should be trivial to test that. Or are you asking what the difference wrt the `setSomeState()` call is when you do the scheduling yourself? – Bergi Dec 09 '20 at 13:56
  • @Bergi: More the former, but as always, it would be ideal to understand all pros/cons to make an informed decision ;). – bluenote10 Dec 09 '20 at 14:25

1 Answers1

-1

Some important points that I would like to cover here before answering your question are as follows:

  1. We can easily do asynchronous calls within useEffect() hook.

  2. The call-back function within useEffect() cannot be async, so callback cannot use async-await, because useEffect() cannot return a promise which every async function does by default:

    useEffect(async ()=>{
    })
    // This will return error and will not work
    
  3. So we can set async function inside the callback function or outside in our component.

Now coming to your question:

useEffect(() => {
  setTimeout(() => {
    setSomeState(complexComputation(someDependency));
  }, 0);
}, [someDependency]);

The main point here is that our useEffect() hook will run initially, and setTimeout() function is simply passed to the Web-API of Browser, Timer starts normally and only once all the code is executed on the call-stack, the callback within setTimeout gets executed.

Check running the below code.

useEffect(() => {
 console.log("Line one")
  setTimeout(() => {
    setSomeState(complexComputation(someDependency));
    console.log("line two");
  }, 3000);
  console.log("Line three");
}, [someDependency]);



Output will be:
Line one
Line Three
Line two // after all other synchronous consoles are executed :)

The question is not about, "what running of useEffect() asynchronously or synchronously mean", but about in what scenarios does useEffect() run first, which runs in both the scenarios.

  1. And since in your code you as using the second argument and passing the value (a state). i-e.,

     useEffect(()=>{},[someDependency])
    

Whenever someDependency again the component re-renders and our useEffect() is again invoked and the code (asynchronous or synchronous) gets executed.

Imran Rafiq Rather
  • 7,677
  • 1
  • 16
  • 35
  • 1
    I understand the order of execution and the dependency handling. What I still couldn't answer is if it is advisable to turn a slow blocking computation into non-blocking one? The answer to that largely depends on whether React already does it internally, right? – bluenote10 Dec 09 '20 at 14:40
  • @bluenote10 I doubt react runs the effects asynchronously in a separate macrotask. I would, however, expect it to batch multiple effects and state changes together. So if you need to render something immediately without waiting for the heavy computation, use the timeout, but I'd expect this to lead to content bouncing around after a split second. – Bergi Dec 09 '20 at 14:46