I was checking how useCallback and useMemo work and how this helps to optimized the performance of the app.
I have created a simple app to understand. What I found is that the callback (OnBlurHandler) which I am passing to my child component is causing the rendering of the component every time the parent component is rendering. So to optimized it I wrapped that callback in useCallback to avoid the re-rendering.
But to my surprise the component still renders even after the using the useCallback. So, I wrap the child component in React.memo and I am able to achieve what I want.
But I am not able to understand why the child component is rendering just using the useCallback. Why do we need to wrap the child component into react.memo.
// Parent Component
import React, { useState, useEffect, useMemo, useCallback } from "react";
import Input from "./Input";
const UnderStadingHooks = () => {
console.log("Component Start");
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const [rdm, setRdm] = useState(0);
let c;
useEffect(() => {
console.log("UseEffect Start");
c = a + b;
console.log(a, b, c, d);
}, [a, b]);
const d = useMemo(() => {
console.log("UseMemo Called");
return a + b;
}, [a, b]);
const OnBlurHandler = useCallback(
(e) => {
console.log("OnBLur Hablder called");
if (e.target.name === "a") {
setA(parseInt(e.target.value));
} else {
setB(parseInt(e.target.value));
}
},
[a, b]
);
const handleOnClick = () => {
const ran = Math.ceil(Math.random() * 5);
setRdm(ran);
};
console.log("Component END");
return (
<div>
<h1>Understanding Hooks In Depth</h1>
<Input
name="a"
placeholder="Enter a Value of A"
OnBlurHandler={OnBlurHandler}
/>
<Input
name="b"
placeholder="Enter a value of B"
OnBlurHandler={OnBlurHandler}
/>
<p>
Values are :{a},{b}
</p>
<p>SumEffect:{c}</p>
<p>SumMemo:{d}</p>
<button type="button" onClick={handleOnClick}>
Display Some Random Number
</button>
<p>{rdm}</p>
</div>
);
};
export { UnderStadingHooks };
// Child Component
import React, { memo } from "react";
const Input = ({ name, placeholder, id, OnBlurHandler }) => {
console.log("Input Component Rendered");
return (
<input
id={id}
name={name}
placeholder={placeholder}
onBlur={OnBlurHandler}
/>
);
};
export default memo(Input);
That's a very common misunderstanding actually. The thing is when a parent component re-renders, it triggers the re-rending of its children (cascading to all its descendants). That happens no matter what props actually changed.
So if you want to avoid the re-rendering of a child component, you need to do both the things you did:
memo
on your component in order to check if any prop changed using shallow equality (===).useCallback
,useMemo
hooks come into play.Further reading/digging
I wasn't able to find official docs where this behaviour is well presented, however you can see more on this reddit thread, which contains useful links if you want to dig into the react rendering process.
A very interesting note is that this behavior is usually misunderstood by a lot of people coming from the redux world, because redux connect applies the memo mechanism (shallow prop comparison) on components by default.