बहुतेक React परफॉर्मन्स ट्युटोरियल्स एकाच चुकीच्या सल्ल्याने संपतात: प्रत्येक गोष्ट useMemo आणि useCallback मध्ये wrap करा आणि काम पूर्ण झाले असे समजा. जर तुम्ही तो सल्ला पाळला असेल, तर तुम्ही कदाचित तुमचे ॲप्लिकेशन अधिक संथ केले असेल. हे hooks मोफत नसतात. प्रत्येक hook मेमरी वाटप करते, dependencies ची तुलना करते आणि कॅश केलेली व्हॅल्यूज साठवते. उद्देशाशिवाय वापरल्यास, ते ऑप्टिमायझेशनऐवजी ओव्हरहेड (अतिरिक्त भार) बनतात.
चला हे सर्व बाजूला सारून प्रत्यक्षात काय महत्त्वाचे आहे ते पाहूया.
प्रत्येक Hook प्रत्यक्षात काय करते
useMemo एक value लक्षात ठेवते. तुम्ही त्याला जड काम करणारा एक function देता आणि ते त्याचे रिझल्ट परत करते. पुढच्या render वेळी, जर तुमच्या dependencies बदलल्या नसतील, तर React ती गणना (calculation) वगळते आणि जुनाच रिझल्ट परत देते.
useCallback एक function लक्षात ठेवते. ते तुमच्यासाठी function चालवत नाही. जोपर्यंत त्याच्या dependencies सारख्याच राहतात, तोपर्यंत ते render मध्ये तोच function instance परत करते.
हाच मुख्य फरक आहे. एक संगणकीय व्हॅल्यू (computed value) कॅश करते, तर दुसरे एक संदर्भ (reference) कॅश करते. या दोघांमध्ये गल्लत केल्यामुळे असा कोड तयार होतो जो ऑप्टिमाइझ केलेला वाटतो, पण प्रत्यक्षात तो unwrapped कोडसारखाच वागतो आणि अतिरिक्त मेमरी खर्च करतो.
Function Identity मुळे तुमचे Tree का बिघडते
जेव्हा एखादा component re-render होतो, तेव्हा React संपूर्ण function body पुन्हा कार्यान्वित (execute) करते. प्रत्येक variable पुन्हा तयार केला जातो. प्रत्येक inline function ला मेमरीमध्ये एक नवीन पत्ता (address) मिळतो.
JavaScript मध्ये, अगदी सारखीच लॉजिक असलेले दोन functions समान नसतात. () => {} === () => {} हे false ठरते. हाच नियम objects आणि arrays ला देखील लागू होतो. जर तुमच्या parent component ने handleSubmit परिभाषित केले आणि ते child ला पास केले, तर प्रत्येक render वेळी त्या child ला एक नवीन prop मिळतो. जरी child React.memo मध्ये wrap केलेला असला, तरी त्याला हे समजू शकत नाही की नवीन function हे जुन्या function सारखेच काम करते. Reference बदलल्यामुळे, child पुन्हा re-render होतो.
हीच ती मूळ समस्या आहे जी सोडवण्यासाठी useCallback बनवले गेले आहे. हे वेगाबद्दल (speed) नाही, तर स्थिरतेबद्दल (stability) आहे.
useMemo कधी फायदेशीर ठरते
जेव्हा तुम्ही असे काम करत असता जे वस्तुनिष्ठपणे खर्चिक (expensive) आहे आणि तुम्हाला मोजता येण्याजोगा विलंब (lag) जाणवत आहे, तेव्हा तुम्हाला useMemo ची गरज असते.
एका मोठ्या dataset ला filter करण्याबद्दल विचार करा. जर तुमच्याकडे हजारो rows असलेला एक table आणि search input असेल, तर तुम्ही तुमच्या component मध्ये काहीसे असे लिहू शकता:
const visibleRows = rows.filter(r => r.name.includes(query));
useMemo शिवाय, तो loop प्रत्येक render वेळी चालतो. जर वापरकर्त्याने sidebar toggle करणारा button क्लिक केला, तर parent re-render होतो आणि rows आणि query कधीही न बदलले असूनही तुमचा filter पुन्हा चालतो. मोठ्या dataset वर, हा अडथळा (stutter) स्पष्टपणे जाणवतो.
useMemo रिझल्ट स्थिर (pin) करून हे सुधारते:
const visibleRows = useMemo(() => {
return rows.filter(r => r.name.includes(query));
}, [rows, query]);
आता React तो filter तेव्हाच पुन्हा चालवते जेव्हा dependencies प्रत्यक्षात बदलतात.
हाच तर्क जटिल गणिती गणना (mathematical calculations), API responses ला chart-friendly formats मध्ये रूपांतरित करणे, किंवा असे state मिळवणे जे अन्यथा सतत पुन्हा मोजले (recalculated) जाईल, या सर्वांना लागू होतो.
याचा दुसरा, कमी स्पष्ट असलेला वापर देखील आहे. जर तुम्ही स्थानिक पातळीवर (locally) एखादा object किंवा array तयार केला आणि तो useEffect dependency array मध्ये समाविष्ट केला, तर तुम्ही चुकून प्रत्येक render वेळी तो effect trigger करू शकता. Inline objects आणि arrays ला प्रत्येक वेळी नवीन identity मिळते, त्यामुळे effect ला बदललेली dependency दिसते आणि तो पुन्हा कार्यान्वित होतो. useMemo वापरून त्या object ला memoize केल्यामुळे reference स्थिर राहतो आणि तुमचा effect केवळ मूळ डेटा प्रत्यक्षात बदलल्यावरच चालतो.
useCallback कधी आवश्यक ठरते
useCallback तेव्हा सर्वात जास्त महत्त्वाचे ठरते जेव्हा तुम्ही React.memo ने optimized केलेल्या child components मध्ये handlers पास करत असता.
एका parent component ची कल्पना करा ज्यामध्ये counter आहे. तो एक expensive child list देखील render करतो:
function Parent() {
const [count, setCount] = useState(0);
const handleItemClick = (id) => {
console.log(id);
};
return (
<div>
<button onClick={() => setCount(c + 1)}>{count}</button>
<ExpensiveList onItemClick={handleItemClick} />
</div>
);
}
प्रत्येक वेळी count बदलल्यावर, Parent re-render होतो. एक नवीन handleItemClick तयार होतो. ExpensiveList ला नवीन prop reference मिळत असल्यामुळे, ते देखील re-render होते. जर ExpensiveList React.memo मध्ये wrap केलेला असेल, तरीही ते memoization पूर्णपणे वाया जाते कारण function prop बदलला आहे.
useCallback reference सुरक्षित ठेवते:
const handleItemClick = useCallback((id) => {
console.log(id);
}, []);
आता ExpensiveList फक्त तेव्हाच re-render होते जेव्हा त्याला खरोखर गरज असते.
आणखी एक गंभीर परिस्थिती useEffect शी संबंधित आहे. जर एखादा effect तुमच्या component मध्ये परिभाषित केलेल्या function ला subscribe करत असेल आणि ते function प्रत्येक render वेळी आपली identity बदलत असेल, तर तो effect वारंवार teardown आणि resubscribe होईल. Function ला memoize केल्यामुळे effect स्थिर राहतो.
Dependency Array चा सापळा आणि Stale Closures
दोन्ही hooks dependency arrays वर अवलंबून असतात आणि इथेच बहुतेक bugs लपलेले असतात.
If you omit a variable from the dependency array, your memoized function or value closes over an old version of that variable. This is a stale closure. The UI might display fresh data, but your callback is still looking at state from three renders ago. The fix is simple but easy to miss during code reviews: include every value used inside the hook that could change.
Run the react-hooks/exhaustive-deps ESLint rule. It will catch obvious omissions. But do not treat it as a robot. Understand why each dependency matters.
The Hidden Tax of Over-Optimization
Beginners often armor every function and every value with these hooks because it feels safe. That habit backfires.
React must store cached values in memory. On every render, it must iterate through your dependency array and compare each item using Object.is. That comparison is cheap, but it is not free. If you wrap a trivial event handler like onClick={() => setOpen(true)} inside useCallback, you are paying memory and CPU costs to avoid creating a function that would have been instant to allocate.
The hooks also add noise. Code wrapped in useMemo and useCallback is harder to read and harder to maintain. Every dependency array is a potential stale closure waiting to bite you.
The real rule of thumb is unglamorous but effective: write plain code first. Optimize only when you have proof of a problem. Use the React DevTools Profiler to identify which components are expensive and which renders are wasteful. If a render takes less than a few milliseconds, no user will notice, and your memoization is solving nothing.
The Bottom Line
useMemo is for expensive values. useCallback is for stable function references. Neither hook makes your component render faster on its own; they prevent unnecessary downstream work. Start without them, measure with actual tooling, and add them precisely where the profiler shows a bottleneck. Clean code that rerenders occasionally will almost always beat over-engineered code that memoizes everything.
