Avoid passing inline functions as a prop in React.
When it comes to React, it’s safe to say that inline functions are evil.
Regarding the above video, it is my first attempt at making a video. I realized explaining things over a video is easier and better compared to writing an article. Would really appreciate any feedback.
I recommend reading the entire article (or watching the video), but for those who want TLDR, here it is
TLDR,; On every re-render of a component, a function gets re-created, and if it’s passed as a props to a child component, it will cause the child to be re-rendered every time the parent rendrs.
Let’s try to understand this more in detail:
Let’s look at this sample React code, which calls a Child
component within App
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
return (
<div className="App">
<p>Value of count is {count}</p>
<Child />
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
I have added console.log
on the Child
component to check the number of times this component gets called.
As expected, with the above code, the child gets rendered only once.
Now, let’s make a slight change to the code.
Let’s add a method to increment the count:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
return (
<div className="App">
<p>Value of count is {count}</p>
<Child />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
In this case when I click Increment
, along with the count getting incremented, the Child
component gets re-rendered. This happens because of the working of diffing alogirthm
(read more in my free GitHub repo for an interview preparation guide)
Let’s try to solve that using React.memo
, a very useful tool provided by React to memoize a component.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
return (
<div className="App">
<p>Value of count is {count}</p>
<MyChild />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
const MyChild = React.memo(Child);
In this code, I have wrapped the Child
component with React.memo
and I am now using MyChild
component in the App.
With this, I have achieved memoization of the Child component and no matter how many times my App component re-renders, the child is not going to re-render…unless…
Unless I pass a prop to the Child
component. Let’s pass a callback function as a prop to the Child
component.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
const callback = () => {}
return (
<div className="App">
<p>Value of count is {count}</p>
<MyChild callback={callback} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
const MyChild = React.memo(Child);
In the above code, I am passing an empty function as a prop to the Child
component.
What happens now when I increment the counter?
As you can see even though theChild
is doing nothing with the function passed, yet, the child component gets re-rendered every time the parent component re-renders.
This is because every time a component re-renders, any function within it gets re-created. (read more about this here)
How can we solve this?
React provides a hook called useCallback
to solve such issues. Let’s update our code to make it more efficient.
import React, { useState, useCallback } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
const callback = useCallback(() => {}, []);
return (
<div className="App">
<p>Value of count is {count}</p>
<MyChild callback={callback} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
const MyChild = React.memo(Child);
In the above code, you can see that I have wrapped the callback function with useCallback
hook provided by React.
Now when I increment the counter, the Child
component doesn’t get re-rendered. The reason is that useCallback
memoizes the function. (I will not be going into detail about the usage of useCallback
here.)
Now, let’s come to the most important part of this blog. As you can see in the above code, we have two functions in our App
component, one being passed as a prop to Child
and the other to onClick
for the button.
If you closely look into both, you will realize that there is a difference in the way both are used. One is passed as a function and in other, we’re using an inline function.
In terms of functionality, there is not much difference between both. I could rewrite the callback function as an inline function and still achieve the same functionality.
import React, { useState, useCallback } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(1);
const callback = useCallback(() => {}, []);
return (
<div className="App">
<p>Value of count is {count}</p>
<MyChild callback={() => callback()} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function Child() {
console.log("Child is getting rendered");
return <h1>I am a child</h1>;
}
const MyChild = React.memo(Child);
But, unknowingly we’ve introduced a bug in terms of performance.
Even though the callback
function is memoized, the anonymous function passed as a prop to the Child component is not memoized, hence, every time the App
re-renders the Child
gets re-rendered.
As you can see, it may not affect the performance for the case like updating a state, but it definitely affects the performance when passing as a prop.
This is a very subtle yet impactful change that if not made carefully, we can lose all the performance enhancement that we would be expecting otherwise by making use of useCallback
react.memo
and other tools.
I hope you found this article useful. I would love to hear your thoughts. 😇
Thanks for reading. 😊
Cheers! 😃
If you find this article useful, you can show your appreciation by clicking on the clap button. As the saying goes, ‘When we give cheerfully and accept gratefully, everyone is blessed’.