Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What comparison process does the useEffect React hook use?

Let's start with my favorite JavaScript expression:

[]==[] // false

Now, let's say what the React doc says about skipping side effects:

You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {/* only runs if 'count' changes */}, [count])

Now let's consider the following component which behavior had made me scratch my head:

const App = () => {

    const [fruit, setFruit] = React.useState('');
    React.useEffect(() => console.log(`Fruit changed to ${fruit}`), [fruit]);

    const [fruits, setFruits] = React.useState([]);
    React.useEffect(() => console.log(`Fruits changed to ${fruits}`), [fruits]);

    return (<div>
        <p>
            New fruit:
            <input value={fruit} onChange={evt => setFruit(evt.target.value)}/>
            <button onClick={() => setFruits([...fruits, fruit])}>Add</button>
        </p>
        <p>
            Fruits list:
        </p>
        <ul>
            {fruits.map(f => (<li key={f}>{f}</li>))}
        </ul>
    </div>)
}

ReactDOM.render(<App/>, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>

When adding 'apple', this is what is being logged in the console:

// on first render
Fruit changed to 
Fruits changed to 

// after each keystroke of 'apple'
Fruit changed to a
Fruit changed to ap
Fruit changed to app
Fruit changed to appl
Fruit changed to apple

// ater clicking on 'add'
Fruits changed to apple

And I don't understand the middle part. After each keystroke, fruits goes from [] to [], which are not the same in JS if they refer to different objects. Therefore, I expected some Fruits changed to to be logged. So my question is:

What is the exact object comparison process used by React to decide on whether or not to skip the effect hook?

like image 355
Nino Filiu Avatar asked Mar 10 '19 16:03

Nino Filiu


2 Answers

A function which is being used to compare objects is practically a polyfill of Object.is method. You can see it here in the source code:

https://github.com/facebook/react/blob/master/packages/shared/objectIs.js

And here's a function which compares prevDeps with nextDeps within useEffect implementation:

https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.new.js#L1427


By the way, Object.is is mentioned as a comparison algorhitm in the hooks API section of the docs, under useState.

like image 86
Pavel Ye Avatar answered Oct 18 '22 05:10

Pavel Ye


After each keystroke, fruits goes from [] to []

It seems that you're under the impression that fruits is re-assigning to a new array after each key stroke which is not the case.

It is not comparing two new arrays, it is comparing the same label, which points to the same reference in the memory in this particular point of time.

Given:

var arr = [];

We can check if arr reference has changed over time (if no mutations took place).

simple example:

var arr = [];
var arr2 = arr;
console.log('arr === arr ', arr === arr)
console.log('arr === arr2 ', arr === arr2)

arr = [];
console.log('---- After the change ----');
console.log('arr === arr ', arr === arr)
console.log('arr === arr2 ', arr === arr2)
like image 28
Sagiv b.g Avatar answered Oct 18 '22 04:10

Sagiv b.g