I'm new with React hooks functional component, I have some questions which need to be explained, thank you:
How do I can perform a deep comparison of some state before re-render? I saw that React.memo only has the 2nd argument for modify a comparison on props only, but how about the state? Currently, it uses Object.is for compare, I want to modify this function.
What is the principles of useCallback, useMemo? I've investigated react source code but still dont know about how can it do this. Does someone give me a simple example to illustrate how it know to cache the value on each render?
Q1: How do I can perform a deep comparison of some state before re-render?
In the snippet below, I show you one way of making a deep comparison of the state.
Q2: What is the principles of useCallback, useMemo?
useMemo
is meant to be used in a situation where you have some expensive calculation that you want to memoize the results. So the expensive calculation only runs once for each unique combination of inputs
.
useCallback
is meant to avoid the re-creation of inner functions that don't need to be recreated. Unless some variable listed in the dependency array changes. It's commonly used as a performance optimization.
As per React DOCS:
useCallback
This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Note:
State changes are meant to cause a re-render. Why would you want to deep compare it before re-rendering? If it's changed, React MUST re-render. That's how it works.
If you want something to change and not cause a re-render, take a look at the useRef
hook. That's exactly what it does. The ref
object remains the same across every render (only changes if the component gets unmounted, and then re-mounted).
https://reactjs.org/docs/hooks-reference.html#useref
useRef
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
(...)
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
SNIPPET
Comparing newState and lastState
function App() {
const [myState, setMyState] = React.useState('Initial state...');
const lastState_Ref = React.useRef(null);
const stateChanged_Ref = React.useRef(null);
if (lastState_Ref.current !== null) {
lastState_Ref.current === myState ? // YOU CAN PERFORM A DEEP COMPARISON IN HERE
stateChanged_Ref.current = false
: stateChanged_Ref.current = true;
}
lastState_Ref.current = myState;
function handleClick() {
setMyState('New state...');
}
return(
<React.Fragment>
<div>myState: {myState}</div>
<div>New state is different from last render: {JSON.stringify(stateChanged_Ref.current)}</div>
<button onClick={handleClick}>Click</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
UPDATE:
From your comment, what you could do regarding props
is to use React.memo
. It will do a shallow compare on your props. See snippet below. You'll see that the regular Child
will re-render every time and the ChildMemo
, which is wrapped in React.memo
won't re-render if the array propA
remains the same.
React.memo
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
That's what React DOCs recommend for that case:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate
function App() {
console.log('Rendering App...');
const [myState,setMyState] = React.useState([1,2,3]);
const [myBoolean,setMyBoolean] = React.useState(false);
return(
<React.Fragment>
<button onClick={()=>setMyBoolean((prevState) => !prevState)}>Force Update</button>
<Child
propA={myState}
/>
<ChildMemo
propA={myState}
/>
</React.Fragment>
);
}
function Child() {
console.log('Rendering Child...');
return(
<div>I am Child</div>
);
}
const ChildMemo = React.memo(() => {
console.log('Rendering ChildMemo...');
return(
<div>I am ChildMemo</div>
);
});
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Regarding state, I don't think you should try to accomplish that. It should re-render if the state has changed. Actually React won't re-render if the state reference (of an object, or array) remains the same. So, if you don't change the state
reference (like re-creating from scratch the same array or object), you already have that behavior out of the box. You can see that on the snippet below:
function App() {
console.log('Rendering App..');
const [myArrayState,setMyArrayState] = React.useState([1,2,3]);
function forceUpdate() {
setMyArrayState((prevState) => {
console.log('I am trying to set the exact same state array');
return prevState; // Returning the exact same array
});
}
return(
<React.Fragment>
<div>My state is: {JSON.stringify(myArrayState)}</div>
<button onClick={forceUpdate}>Reset State(see that I wont re-render)</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>