What does the term "functional components" mean? How to utilize Hooks to manage the re-rendering of React Function components.
While a LifeCycle mechanism and render method exist in the structure of Class Components, these LifeCycle methods are not present in Functional Components, so the rendering process for Functional Components differs from that of Class Components.
In conclusion, there are several ways to render or not render a React Class Component.
React Class Component Lifecycle
This was covered in the last example, How to Control Re-Renders on React Class Components.
There is a magic in the working logic of hooks. Because although there may be more than one transition through the function, this does not mean that the function is rendered on each transition. This is a very confusing approach that hides the background from the Developer.
Hooks useEffect is a hook created to write functions that will be done in the form of where the component should affect the environment of the component after rendering, as well as useState you can keep the current state. There are many Custom Hooks like this and you need to debug the code thoroughly to understand how they trigger the rendering of this component.
It is quite difficult to understand the actual working logic of the components. For more detailed information about React Hooks, I recommend you to read the What Are React Hooks e-book.
When we use ReactQuery, Apollo, React Router etc... libraries, it will be very important for us to understand when the CustomHook structures used with use... trigger the component to re-render.
While some operations in functional components are performed in useEffect, most of them are called at the top of the component and the CustomHook is triggered, so a number of tools have been provided to solve the performance problems caused by repeatedly passing the flow through the function component.
Here, let's start examining our first example with the effect of the State change in the parent component on the child element.
Our first example (https://onurdayibasi.dev/rerender-with-state-func-comp)
https://onurdayibasi.dev/rerender-with-state-func-comp
A Counter state is kept in the Parent Container, let's see the effect of this Counter change on Child Components.
const NoStateAndPropComponent = () => {
console.log("render:NoStateAndPropComponent");
return (
<div style={{ border: "1px solid black" }}>No State and Props Comp</div>
);
};
function AgeHookComponent() {
const [age, setAge] = React.useState(30);
console.log("render:AgeHookComponent");
return (
<div style={{ border: "1px solid black" }}>
Age Hook Component
<br />
<span>Age:{age}</span>
<button onClick={() => setAge(age + 1)}>Increase Age</button>
</div>
);
}
const MemoizedNoStateAndPropComp = React.memo(NoStateAndPropComponent);
const MemoizedAgeHookComp = React.memo(AgeHookComponent);
As can be seen in the example, the Counter change ensures that components without Memoization are re-rendered, while React.memoized components are not affected.
React.memo
is a higher-order component in React that can be used to memoize the rendering of a functional component. Memoization is a technique to optimize performance by caching the result of a function call and returning the cached result when the same inputs occur again. In the context of React, React.memo
helps to prevent unnecessary renders of functional components by memoizing their output.
Here are some points to keep in mind when using React.memo
:
Pure Components: React.memo
works well with pure functional components. If your component depends only on its props and state and doesn't have any side effects, using React.memo
can be beneficial.
const MyComponent = React.memo((props) => {
// render component based on props
});
Shallow Comparison of Props: By default, React.memo
does a shallow comparison of props to determine whether the component should update. If the props have not changed, the component won't re-render. Make sure that your props are either primitive values or that you are handling complex objects in a way that avoids unnecessary re-renders.
Custom Comparison Function: If your component receives complex objects as props and you want to customize the comparison logic, you can provide a custom comparison function as the second argument to React.memo
. This function should return true
if the props are equal and false
otherwise.
const MyComponent = React.memo(
(props) => {
// render component based on props
},
(prevProps, nextProps) => {
// custom comparison logic
return prevProps.someValue === nextProps.someValue;
}
);
Avoiding Unnecessary Memoization: Be cautious about using React.memo
on every component. In some cases, it might be more efficient to let a component re-render if the cost of the render is low. Memoization comes with a cost, and using it excessively can lead to unnecessary complexity.
Performance Profiling: Before and after applying React.memo
, it's a good idea to profile the performance of your application to ensure that the memoization is providing the expected benefits. React DevTools and browser performance tools can be helpful for this purpose.
Remember that while React.memo
can be a useful optimization tool, it's not a silver bullet, and its benefits may vary depending on the specific use case and component structure. Always measure and profile the performance impact of memoization in your application.
Let's improve our scenarios a bit in React.memo versions. Now our components should get props from outside, this props should not only contain title as string but also callback for sayHi function.
function AgeHookComponent(props) {
const [age, setAge] = React.useState(30);
console.log(`render:${props.title}`);
return (
<div style={{ border: "1px solid black" }}>
{props.title}
<br />
<span>Age:{age}</span>
<button onClick={() => setAge(age + 1)}>Increase Age</button>
<button onClick={() => props.sayHi(props.title)}>Say Hi</button>
</div>
);
}
We cover this component with the Memoization structure with React.memo again
const MemoizedAgeHookComp = React.memo(AgeHookComponent);
We will create 3 types of components.
const handleSayHi = (name) => {
console.log(`Hi I'm ${name}`);
};
<MemoizedAgeHookComp title="const handleSayHi" sayHi={handleSayHi} />;
<MemoizedAgeHookComp
title="inline handleSayHi"
sayHi={(name) => console.log(`Hi I'm ${name}`)}
/>
const handleSayHiMemoized = React.useCallback((name) => {
return console.log(`Hi I'm ${name}`);
}, []);
This example is available at (https://onurdayibasi.dev/rerender-with-props).
https://onurdayibasi.dev/rerender-with-props
As can be seen in the image above, as a result of increasing Counter, the following components are re-rendered, although they are not related to this value increase.
Besides that
This is because while React.memo works for primitive values like string in shallow comparison, it does not work for object, array, functions props preventing unnecessary re-rendering. Since the address of the sayHi function changes in the parent -> child progression during each render, React.memo cannot detect it.
For this, React.useCallback is used to ensure that the function address passed as a prop does not change.
React.useCallback
is a hook in React that is used to memoize functions. It returns a memoized version of the callback function that only changes if one of the dependencies has changed. This can be particularly useful in scenarios where you want to avoid unnecessary recreations of functions, especially when passing them down to child components.
The basic syntax for useCallback
is as follows:
jsxCopy code
const memoizedCallback = React.useCallback(
() => {
// function logic
},
[/* dependencies */]
);
Here, the first argument is the function you want to memoize, and the second argument is an array of dependencies. The callback will only be re-created if one of the dependencies has changed.
Here's a breakdown of key points:
Memoization: useCallback
memoizes the provided function. This means that if the dependencies have not changed, it returns the same function reference on subsequent renders.
Dependencies Array: The second argument to useCallback
is an array of dependencies. If any of the dependencies change between renders, the callback function will be re-created. If the dependencies array is empty, the callback will only be created once and will not depend on any values.
jsxCopy code
const memoizedCallback = React.useCallback(() => {
// function logic
}, [dependency1, dependency2]);
Use Cases: useCallback
is commonly used in conjunction with the React.memo
higher-order component to optimize the performance of child components. When a parent component renders, it can pass down a memoized callback to its children, preventing unnecessary re-renders of the child components if the callback hasn't changed.
jsxCopy code
const ParentComponent = () => {
const handleClick = React.useCallback(() => {
// handle click logic
}, [/* dependencies */]);
return <ChildComponent onClick={handleClick} />;
};
const ChildComponent = React.memo(({ onClick }) => {
// render component based on props
});
Avoiding Unnecessary Renders: By using useCallback
with React.memo
, you can optimize performance by preventing the recreation of callback functions and unnecessary renders of components when the props haven't changed.
Remember to use useCallback
judiciously and only when needed, as there can be cases where the overhead of memoization outweighs the benefits, especially for simple and lightweight functions.
Whether you have a component like the one below, functions that are highly computationally intensive or take a long time to pull data from an API will have a high burden on component rendering costs.
function Counter(props) {
console.log(`Counter not useMemo`);
const computeExpensiveValue = (maxNumber) => {
console.log(`Counter not useMemo : calcTotal run with ${maxNumber}`);
let total = 0;
for (let i = 0; i < maxNumber; i++) {
total += i;
}
return total;
};
const result = computeExpensiveValue(100000....);
return (
<div style={{border: '1px solid black'}}>
{props.title}
{result};
<br />
</div>
);
For this, we can cache the related function with React.useMemo to shorten the long waiting times.
const result = React.useMemo(() => computeExpensiveValue(100000), []);
I developed an application for this (https://onurdayibasi.dev/rerender-with-use-memo)
https://onurdayibasi.dev/rerender-with-use-memo
Rendering time of the component with memoization applied
Each render time in a component without React.useMemo applied
React.useMemo
is a hook in React that is used to memoize the result of a computation. It is particularly useful when you need to perform a costly calculation and you want to avoid recomputing the result on every render.
The basic syntax for useMemo
is as follows:
jsxCopy code
const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);
Here, the first argument is a function that performs the computation, and the second argument is an array of dependencies. The memoized value will only be recomputed if one of the dependencies has changed.
Key points about useMemo
:
Memoization: useMemo
memoizes the result of the provided function. If the dependencies have not changed since the last render, it returns the cached result without recomputing it.
Dependencies Array: The second argument to useMemo
is an array of dependencies. If any of the dependencies change between renders, the memoized value will be recomputed. If the dependencies array is empty, the value will only be computed once and will not depend on any values.
jsxCopy code
const memoizedValue = React.useMemo(() => {
// compute value based on dependencies
}, [dependency1, dependency2]);
Use Cases: useMemo
is often used to optimize performance in situations where a computation is expensive and the result doesn't need to be recalculated on every render. Common use cases include complex calculations, data filtering, and formatting.
jsxCopy code
const MemoizedComponent = () => {
const memoizedValue = React.useMemo(() => {
// perform expensive calculation
return computeExpensiveValue(data);
}, [data]);
// render component using memoizedValue
};
Avoiding Unnecessary Recalculations: By using useMemo
, you can optimize performance by preventing the unnecessary recalculation of values, especially in scenarios where the computation is resource-intensive.
It's important to note that while useMemo
can be a valuable optimization tool, it's essential to use it judiciously. Memoization comes with a cost, and in some cases, the overhead of memoization might outweigh the benefits, especially for lightweight computations. Always measure and profile the performance impact in your specific use case.
You may get the source code to better understand how it works by clicking on the adjacent download button.