🧠 “My component re-renders even though nothing has changed... WHY?”
If you've asked this question while working with React, you’re not alone.
In this article, we’ll walk through a real performance issue caused by using arrow functions inside JSX. You’ll learn:
- ✅ Why it's a bad practice
- ✅ How it affects
React.memo
,useCallback
, and re-renders - ✅ A working demo that proves the impact
- ✅ Clean code patterns that make you look like a React pro
🔍 The Problem: New Function on Every Render
When you define an arrow function directly inside JSX, like this:
<button onClick={() => console.log("Clicked")}>Click</button>
You're actually creating a brand new function every time the component re-renders.
Even if the logic inside is exactly the same — the function identity is different.
That means React.memo
can’t help you.
That means your child components might re-render unnecessarily.
That means your app is doing more work than it should.
🔬 The Test Case: Counter + Child Component
Let’s build a small project to visualize this problem.
👇 The Setup:
- A parent with a counter (
useState
) - A child that receives a
handleClick
function - We'll test with
React.memo
to see if the child re-renders
💣 Problematic Code:
import React, { useState, memo } from "react";
const Child = memo(({ onClick }) => {
console.log("🔄 Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increase</button>
{/* ❌ This function is re-created on every render */}
<Child onClick={() => console.log("Child Clicked")} />
</div>
);
}
🧨 Every time the "Increase" button is clicked, the Child component re-renders... even though its content didn’t change.
✅ The Clean Fix: Define Handlers Outside JSX
Here’s the correct pattern:
import React, { useState, memo } from "react";
const Child = memo(({ onClick }) => {
console.log("🔄 Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// ✅ Defined outside JSX → Stable function reference
const handleChildClick = () => {
console.log("Child Clicked");
};
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increase</button>
{/* ✅ Now Child won't re-render unnecessarily */}
<Child onClick={handleChildClick} />
</div>
);
}
🎯 Console Output:
- Now, when clicking “Increase”, the child does not re-render ✅
🧠 What’s Actually Happening?
Pattern | Function Identity | React.memo Impact | Performance |
---|---|---|---|
() => console.log() inside JSX |
🆕 New every time | ❌ Breaks memo | 🔻 Bad |
const fn = () => {} outside JSX |
✅ Stable | ✅ Memo works | 🚀 Good |
🛡️ Bonus: useCallback
for Dynamic Handlers
Sometimes you can't avoid inline functions (e.g., if the handler depends on props).
Use useCallback
to keep the reference stable:
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
This helps avoid unnecessary renders when passing handlers to deeply nested children.
🔥 Final Thoughts
React is fast — but it can’t optimize what you write inefficiently.
Defining arrow functions inside JSX might look clean, but behind the scenes, it’s causing:
- 🌀 New functions every render
- 📉 React.memo being bypassed
- ⚠️ Performance loss in large trees
✅ The fix is simple: move the function outside JSX, or use useCallback
when necessary.
Learn this now, and your future React self will thank you.
⚡ "ليه الـ Component بتاعي بيعمل Re-render رغم إن مفيش حاجة اتغيرت؟"
🧠 "My component re-renders even though nothing has changed... WHY?"
لو سألت نفسك السؤال ده وأنت شغال بـ React، فأنت مش لوحدك 👀
في المقال ده، هنتكلم عن مشكلة أداء حقيقية بتحصل بسبب إنك بتستخدم arrow function جوه JSX.
وهنعرف:
- ✅ ليه دي ممارسة سيئة
- ✅ إزاي بتأثر على
React.memo
، وuseCallback
، والـ re-renders - ✅ مثال عملي يوضح المشكلة
- ✅ أنماط كتابة أنضف (Clean Patterns) هتخليك تكتب زي المحترفين
🔍 المشكلة: كل Render بيولد Function جديدة
لما تكتب arrow function مباشرة داخل JSX، زي كده:
<button onClick={() => console.log("Clicked")}>Click</button>
ده معناه إنك بتولد function جديدة كل مرة الـ component بيعمل render.
حتى لو نفس الكود بالضبط – الـ identity بتاع الـ function مختلفة.
وده معناه إن React.memo
مش هيقدر يمنع إعادة الرسم.
وده معناه إن ممكن مكونات الأبناء تعمل re-render من غير سبب.
وده معناه إن الأبليكيشن بيعمل شغل أكتر من اللازم.
🔬 الحالة التجريبية: Counter + Child Component
تعالى نعمل مشروع صغير يوضح المشكلة بشكل عملي.
👇 مكونات التجربة:
- مكون أب (Parent) فيه عداد (باستخدام
useState
) - مكون طفل (Child) بياخد منه دالة
handleClick
- هنختبر باستخدام
React.memo
هل الطفل فعلاً بيعيد الرسم ولا لأ
💣 كود فيه مشكلة:
import React, { useState, memo } from "react";
const Child = memo(({ onClick }) => {
console.log("🔄 Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increase</button>
{/* ❌ الـ function دي بتتولد جديدة في كل render */}
<Child onClick={() => console.log("Child Clicked")} />
</div>
);
}
🧨 كل مرة تدوس على زر "Increase"، المكون الطفل بيعمل re-render… رغم إن مفيش أي تغيير فعلي في الـ props.
✅ الحل النظيف: عرف الدوال خارج JSX
وده الشكل الصح للكود:
import React, { useState, memo } from "react";
const Child = memo(({ onClick }) => {
console.log("🔄 Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// ✅ معرف بره JSX → ثابت في كل render
const handleChildClick = () => {
console.log("Child Clicked");
};
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increase</button>
{/* ✅ كده Child مش هيعمل re-render من غير داعي */}
<Child onClick={handleChildClick} />
</div>
);
}
🎯 النتيجة في الـ Console:
- لما تدوس "Increase"، الطفل مش هيعمل re-render ✅
🧠 إيه اللي بيحصل فعليًا؟
الحالة | هوية الـ Function | تأثيرها على React.memo
|
الأداء |
---|---|---|---|
() => console.log() جوه JSX |
🆕 جديدة كل مرة | ❌ بتكسر memo | 🔻 ضعيف |
const fn = () => {} بره JSX |
✅ ثابتة | ✅ memo بيشتغل | 🚀 ممتاز |
🛡️ مكافأة: استخدام useCallback
مع دوال ديناميكية
في بعض الحالات، بتضطر تستخدم دالة جوه JSX (مثلاً لو بتعتمد على props).
هنا تقدر تستخدم useCallback
عشان تحافظ على ثبات الـ reference:
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
وده بيمنع إعادة الرسم غير الضرورية لما تبعت الدالة لأطفال بعيدين في الشجرة (deep children).
🔥 خلاصة
React سريع، لكن مش هيقدر يعمل Optimizations لو الكود نفسه مش مكتوب بكفاءة.
كتابة arrow functions داخل JSX شكلها نظيف، لكن بتسبب:
- 🌀 دوال جديدة في كل render
- 📉 تعطيل
React.memo
- ⚠️ مشاكل أداء حقيقية في المشاريع الكبيرة
✅ الحل بسيط: حط الدالة خارج JSX، أو استخدم useCallback
لما يكون ضروري.
اتعلم النقطة دي دلوقتي، ومستقبلك في React هيشكرك عليها 🙌
Top comments (0)