Why React Re-renders Matter
If you’ve been working with React for a while, you’ve probably heard the term “re-render” thrown around quite a bit. But what exactly does it mean — and why should you care?
Why ?
React re-renders a component each time it believes something is changed, a new prop, updated state, or more recently, a context value. React re-rendering means React will call your component function, create a new version of the UI, and compare it to the last one using the virtual DOM it maintains. It will then diff the changes and update the real DOM as necessary.
For example, when you are working with small projects the overall process for rendering UI is so small and fast, that we hardly notice it occurring, but when our app starts to grow, and we use nested and deeply nested components and passing props and using a large dataset unnecessary renders will definitely impact our app’s performance. Unnecessary re-renders are when a component re-renders because of an update is happening somewhere else in the app, when the updates don’t actually affect or change it at all. This usually doesn’t matter too much to start, but over time it adds up and can make a really big difference as your app gets bigger.
For example: Think about it like this:
let’s say you’re in your kitchen making a sandwich. Every time you pick up a piece of an ingredient like bread or cheese, a person comes in and cleans out your entire kitchen from top to bottom, even when it really doesn’t need it, because they assume you made a mess.
So, while React is smart, it doesn’t always know when to relax. That’s where tools like memo
, useMemo
, and useCallback
help—they let us tell React, “No need to touch this unless something actually changes.
lets understand memo , useMemo and useCallback seperately then will create a scenario and understand combined
memo
(for component memoization)useMemo
(for memoizing expensive calculations)useCallback
(for memoizing functions)
React.Memo
memo
is a higher-order component (HOC) that memoizes a React component. It prevents re-renders if the props haven’t changed.
Problem Without memo
By default, React re-renders a child component whenever its parent re-renders—even if the child’s props haven’t changed! This can lead to unnecessary performance overhead.
Solution: Using memo
Wrap your component with memo
to optimize re-renders.
MemoComp.tsx
import { memo } from "react";
const MemoComp= ({ value }: any) => {
console.log(value);
return <div>Memo</div>;
};
export default MemoComp
App.tsx
import React, { useState } from "react";
import MemoCompfrom "./MemoComp";
import Input from "./Input";
const Example = () => {
const [count, setCount] = useState<number>(0);
const [inputValue, setInputVale] = useState<string>("");
const onChangeInput = (value: string) => {
setInputVale(value);
};
const incrementCount = () => {
setCount(c => c + 1);
};
return (
<div>
<Input value={inputValue} onChange={onChangeInput}/>
<Memo value={count} />
<button onClick={incrementCount}>Increment Count: {count}</button>
</div>
);
};
export default Example;
What Happens Without memo
?
Case 1: Typing in the Input Field
App
re-renders → BecauseinputValue
state was updatedInput
re-renders → Expected behavior since its props changedMemoComp
also re-renders → Even though its props did not change!
Case 2: Clicking “Increment Count” Button
App
re-renders → Becausecount
state was updatedInput
re-renders → Even though its props did not changeMemoComp
re-renders → Even though its props did not change
Why Does This Happen?
React’s default behavior is to re-render all child components when a parent component re-renders. This happens even if:
- The child component’s props remain unchanged
- The child component’s rendered output would be exactly the same
In our example:
MemoComp
receives a prop (count
)- Yet it still re-renders unnecessarily whenever
App
re-renders
Solution
import React from 'react';
interface MemoCompProps {
message: string;
}
const MemoComp = ({ message }: MemoCompProps) => {
console.log('MemoComp rendered - WITH memo');
return <div>Static Message: {message}</div>;
};
export default React.memo(MemoComp); // Now memoized!
useCallback
In JavaScript, a callback function is a function that is passed as an argument to another function and is executed after some event or action has occurred.
Callback functions work the same way in the React context as in JavaScript, with the addition of being passed as props to child components. This allows children components to communicate with the parent component, enabling a flow of data and actions up the component tree. lets undestand with example.
Continue with above example
Input.tsx
import React, { memo } from "react";
interface InputProps {
value: string;
onChange: (value: string) => void;
}
const Input = ({ value, onChange }: InputProps) => {
console.log("rendering");
return (
<div>
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
};
export default memo(Input);
According to example . we have Memoized both Memo and Input component also means when we will start typing input component should render and when increase count MemoComp should render. but its not behaving like this.
why ?
In this example we are passing onChange and value as a props .
In Javascript when we compare two same function without return type it will be false.
const f1=()=>{
let sum =1+2
}
const f2 =()=>{
let sum =1+2
}
if we console.log(f1 ==f2 ) ? ?????? answer = false
In JavaScript, functions are objects, and each function has its own unique reference in memory, even if the contents (the body) of the function are the same or very similar.
- f1 and f2 are two separate function objects.
- Comparing them using == (or ===) checks whether they refer to the same function object, not whether they do the same thing.
- Since f1 and f2 are defined separately, they have different references → so the comparison returns false
- So when when Input Comapare prev props and present props it then it will be false that’y rerender the component
Soltion :
const onChangeInput = (value: string) =>{
setInputVale(value);
};
To
const onChangeInput =useCallback((value: string)=>{
setInputVale(value);
},[])
will Solve the issue.
useMemo
useMemo is a React Hook that memoizes expensive computations, only recalculating them when their dependencies change. It serves two key purposes:
Performance Optimization: Skip expensive recalculations
Referential Stability: Maintain same object/array reference between renders
import React, { useState } from "react";
let LENGTH = 29999999;
const intialItems = new Array(LENGTH).fill(0).map((_, i) => {
return {
id: i,
selected: i == LENGTH - 1,
};
});
interface useMemoProps {}
export default function UseMemo({}: useMemoProps) {
const [count, setCount] = useState(0);
const [items] = useState(intialItems);
// Problematic line - runs on every render
const selectedItem = items.find(item => item.selected);
return (
<div>
<p>Counts : {count}</p>
<p>SelectedItem :{selectedItems?.id}</p>
<button onClick={() => setCount(count + 1)}>inc➕</button>
</div>
);
}
we have component where we are showing selected items which is last element of array , our array data set is very large
when we click on count button fastly we get glitches on increase of count .
What’s Happening?
- Every time you click the button:
count
state updates → component re-renders- The
.find()
operation runs again through 29 million items - JavaScript blocks the main thread → UI feels “glitchy”
Solution
const selectedItem = useMemo(
() => items.find(item => item.selected),
[items] // Dependency array
);
Memoization: useMemo caches the result
Smart Recalculation: Only recomputes if items changes (which it never does)
Smooth UI: Button clicks respond instantly