How Does Shallow Compare Work in React

How does shallow compare work in react

Shallow compare does check for equality. When comparing scalar values (numbers, strings) it compares their values. When comparing objects, it does not compare their attributes - only their references are compared (e.g. "do they point to same object?").

Let's consider the following shape of user object

user = {
name: "John",
surname: "Doe"
}

Example 1:

const user = this.state.user;
user.name = "Jane";

console.log(user === this.state.user); // true

Notice you changed users name. Even with this change, the objects are equal. The references are exactly the same.

Example 2:

const user = clone(this.state.user);
console.log(user === this.state.user); // false

Now, without any changes to object properties they are completely different. By cloning the original object, you create a new copy with a different reference.

Clone function might look like this (ES6 syntax)

const clone = obj => Object.assign({}, ...obj);

Shallow compare is an efficient way to detect changes. It expects you don't mutate data.

Does React apply a shallow/deep compare in hooks's dependency array?

Will React recompute the value given by the factory function if the
reference to obj changes?

From what I know and understand, React uses shallow reference equality checks.

Consider the following code:

function App() {
const [c, setC] = React.useState(0);
const [obj, setObj] = React.useState({ prop: 10 });

const forceRender = () => setC((c) => c + 1);

const memoizedValue1 = React.useMemo(() => {
console.log("`obj` dependency updated, recomputing value.");
return obj.prop;
}, [obj]);

const memoizedValue2 = React.useMemo(() => {
console.log("`obj.prop` dependency updated, recomputing value.");
return obj.prop;
}, [obj.prop]);

const immutableHandler = () => {
setObj(() => {
console.log("Immutable update returns new object.");
return { prop: 42 };
});
forceRender();
};

const mutableHandler = () => {
setObj((obj) => {
console.log("Mutable update returns same object with updated property.");
obj.prop = 13; // DON'T DO THIS IN REAL CODE, IT'S A MUTATION!!
return obj;
});
forceRender();
};

return (
<div className="App">
<div>memoizedValue1: {memoizedValue1} - memoizedValue2: {memoizedValue2}</div>
<button type="button" onClick={immutableHandler}>
Immutable Update
</button>
<button type="button" onClick={mutableHandler}>
Mutable Update
</button>
<button type="button" onClick={() => setObj({ prop: 10 })}>
Reset
</button>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />

How does React shallowCompare work?

shallowCompare is a legacy add-on. Use React.PureComponent instead.
https://facebook.github.io/react/docs/shallow-compare.html

If there are no changes in state or props, shallowCompare returns false. And of course, when there are changes, shallowCompare returns true, and proceeds with the re-render of the Component.

But if you have a deeply nested object, shallowCompare will not be able to tell that the nested objects have updated/changed.

You either write your own function to check if the object has changed, or you can use a very naive method to check for changes if the ORDER of the properties is NOT important.

JSON.stringify(obj1) === JSON.stringify(obj2) 

I personally don't recommend using the shallowCompare or React.PureComponent because the use case is too narrow. If you have a complex object, neither function is effective. If your function works for you, then use it.

Why doesn't React shallow comparisons notice references' changes?

this.state.words physically change (the previous object is replaced by a new one, so new pointer

It doesn't.

The first line just copies the reference. Both this.state.words and words point to the same array. Do a words.push('marklar') and you'll see this.state.words.length change as well.

const words = this.state.words;

When you do setState afterwards, you just "overwrite" state.words with words, but this doesn't really do anything; you keep the same reference. It's essentially a no-op.

Strict Equality (===) versus Shallow Equality Checks in React-Redux

With connect, mapStateToProps returns a composite object of all of the state being selected from the store, so a shallow comparison across its keys makes sense. With useSelector, the pattern is often to only return a single value for each invocation of useSelector, similar to the way that the useState hook only handles a single value instead of all of the state values. So if each call to useSelector returns a value directly then a strict equality check makes sense vs a shallow comparison. A short example should make this more clear.

import {connect} from 'react-redux';

const mapStateToProps = state => (
{keyA: state.reducerA.keyA, keyB: state.reducerB.keyB}
);
export default connect(mapStateToProps)(MyComponent);

Here mapStateToProps is called every time the store changes in any way, so the return value will always be a new object, even if keyA and keyB do not change. So a shallow comparison is used to decide if a re-render is needed.

For the hooks case:

import {useSelector} from 'react-redux';

function MyComponent(props) {
const keyA = useSelector(state => state.reducerA.keyA);
const keyB = useSelector(sate => state.reducerB.keyB);
...
}

Now the result of the useSelector hooks are the individual values from the store, not a composite object. So using strict equality as the default here makes sense.

If you want to use only a single useSelector hook that returns a composite object, the docs you linked to have an example of using the shallowEqual equality function: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates



Related Topics



Leave a reply



Submit