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
Access JavaScript Nested Objects Safely
Uncaught Syntaxerror: Unexpected Token with JSON.Parse
What's the Fastest Way to Convert String to Number in JavaScript
Does JavaScript Support 64-Bit Integers
Why Use Jquery On() Instead of Click()
Localstorage Access from Local File
Embed an External Page Without an Iframe
Extract the Text Out of HTML String Using JavaScript
JavaScript on the Bottom of the Page
Angular2/Spring Boot Allow Cross Origin on Put
What's the Best Way to Retry an Ajax Request on Failure Using Jquery
Window.Open(Url, '_Blank'); Not Working on Imac/Safari
What the Difference of This.State and This.Setstate in Reactjs
React Router with Optional Path Parameter
Internet Explorer 11 Detection
Reformat String Containing Date with JavaScript
How to Use JSON Data to Populate the Options of a Select Box