Can someone explain this to me from event loop perspective?

Viewed 237

I have the following React snippet here

****** Example.js *******

    import React from 'react';

    export default const SampleApp =  props => {
        const [text, setText] = React.useState('Some name in here');

        console.log(`[1] The value of the text is - ${text}`); // ---(1)

        React.useEffect(() => {
            setText('Ohhh yeah ma boy');
            console.log(`[2] The value of the text is - ${text}`); // --- (2)
        }, []);

        React.useEffect(() => {
            console.log(`[3] The value of the text is - ${text}`); // --- (3)
            setText('Yare yare daze');
        }, [text]);

        console.log(`[4] The value of the text is - ${text}`); // --- (4)

        return (
            <div>{text}</div>
        )
    }

When i run it, it gives me the following console messages

    [1] The value of the text is - Some name in here
    [4] The value of the text is - Some name in here
    [2] The value of the text is - Some name in here
    [3] The value of the text is - Some name in here
    [1] The value of the text is - Yare yare daze
    [4] The value of the text is - Yare yare daze
    [3] The value of the text is - Yare yare daze
    [1] The value of the text is - Yare yare daze
    [4] The value of the text is - Yare yare daze

I don't get it why 'Ohhh yeah ma boy' value did not appear in the console message?

Is it because of the same reason as to why the following code yields the value 5 5 5 5 5 on the console message when i run it though i expect it to show 0 1 2 3 4?

for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

Meaning that on the event loop, useState is already updated 2 times on the 2nd rerendering, while the second useEffect is still on queue?

1 Answers

In react, all state updates enqueued during a render cycle are asynchronously processed in the order they enqueued in, and are batch processed.

Given the provided code:

const SampleApp =  props => {
    const [text, setText] = React.useState('Some name in here');

    console.log(`[1] The value of the text is - ${text}`); // ---(1)

    React.useEffect(() => {
        setText('Ohhh yeah ma boy');
        console.log(`[2] The value of the text is - ${text}`); // --- (2)
    }, []);

    React.useEffect(() => {
        console.log(`[3] The value of the text is - ${text}`); // --- (3)
        setText('Yare yare daze');
    }, [text]);

    console.log(`[4] The value of the text is - ${text}`); // --- (4)

    return (
        <div>{text}</div>
    )
}

On the initial render cycle the output will be

[1] The value of the text is - Some name in here
[4] The value of the text is - Some name in here
[2] The value of the text is - Some name in here
[3] The value of the text is - Some name in here
  1. The [1] console ran as an unintentional side-effect with state 'Some name in here', then the [4] console ran also ran as an unintentional side-effect with state 'Some name in here'. Once the render completes the useEffect hooks are run, first the "onMount" effect logging [2] then the second logging [3], both with state 'Some name in here'.
  2. The mounting effect hook enqueues a state update 'Ohhh yeah ma boy'
  3. The second effect enqueues a state update 'Yare yare daze'
  4. Both enqueued updates trigger a rerender and are processed before the next render cycle. State is updated with 'Ohhh yeah ma boy', and then updated with 'Yare yare daze'.

The next render cycle the output will be

[1] The value of the text is - Yare yare daze
[4] The value of the text is - Yare yare daze
[3] The value of the text is - Yare yare daze
  1. The [1] console ran as an unintentional side-effect with state 'Yare yare daze', then the [4] console ran also ran as an unintentional side-effect with state 'Yare yare daze'. Once the render completes the second useEffect hook runs, logging [3] with state 'Yare yare daze'.
  2. The second effect hook enqueues a state update 'Yare yare daze'.
  3. The enqueued update triggers a rerender and is processed before the next render cycle. State is updated with 'Yare yare daze'.

The next "render cycle" the output will be

[1] The value of the text is - Yare yare daze
[4] The value of the text is - Yare yare daze

Notice here that I've placed quotes around the render cycle.

  1. As part of React's reconciliation process the component is rendered during the "render phase" in order to compute a ReactTree diff. Since the state is the same value as the previous render result, react is Bailing out of a state update.
  2. The [1] console ran as an unintentional side-effect with state 'Yare yare daze', then the [4] console ran also ran as an unintentional side-effect with state 'Yare yare daze'.

The following react lifecycle diagram should help differentiate the "render phase" when the component is rendered out to compute ReactTree diffs from the "commit phase" when the component is rendered out to the DOM. Note that effects are run in the "commit phase"

enter image description here

Questions

I don't get it why 'Ohhh yeah ma boy' value did not appear in the console message?

As explained above, the first enqueued state update is overwritten by the second enqueued state update, so you just simply won't ever see it.

Is it because of the same reason as to why the following code yields the value 5 5 5 5 5 on the console message when i run it though i expect it to show 0 1 2 3 4?

for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

No, not really. In this example you instantiate 5 timeouts and then mutate the variable i being logged. In other words, each timeout callback references the same variable (and value in memory) and the for-loop mutates the value, so when the timeout expires they all log the current value of i, which is 5.

Related