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>
            New fruit:
            <input value={fruit} onChange={evt => setFruit(evt.target.value)}/>
            <button onClick={() => setFruits([...fruits, fruit])}>Add</button>
            Fruits list:
            {fruits.map(f => (<li key={f}>{f}</li>))}

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?

2 Answers

Powell_v2 On Best Solutions

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:


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


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

Sagiv b.g On

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.


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)