
ReactJS
Event handlers are called frequently. Learn best practices to avoid performance problems and unnecessary re-renders.
Creating new functions on every render can impact performance. Use named functions when possible:
// ❌ Less efficient: new function every render
function ListBad() {
const items = [1, 2, 3];
return (
<ul>
{items.map((item) => (
<li
key={item}
onClick={() => console.log("Clicked:", item)}
>
{item}
</li>
))}
</ul>
);
}
// ✅ Better: use named function
function ListGood() {
const items = [1, 2, 3];
const handleItemClick = (item) => {
console.log("Clicked:", item);
};
return (
<ul>
{items.map((item) => (
<li
key={item}
onClick={() => handleItemClick(item)}
>
{item}
</li>
))}
</ul>
);
}Actually, for simple cases, inline functions are fine. The key is avoiding re-renders of child components.
<details>
<summary>📚 More Examples</summary>
// For child components that memoize, use useCallback
import { useCallback } from "react";
function ParentWithMemo() {
const handleClick = useCallback((id) => {
console.log("Clicked:", id);
}, []); // Empty dependency array = function never changes
return (
<div>
<MemoizedChild onClick={handleClick} />
</div>
);
}
// The child can use memo to prevent re-rendering
const MemoizedChild = memo(({ onClick }) => {
// Only re-renders if onClick changes
return <button onClick={() => onClick(1)}>Click</button>;
});</details>
Creating new objects in handlers can trigger unnecessary updates:
// ❌ Avoid: creating new object on every click
function BadForm() {
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
name: e.target.name.value,
email: e.target.email.value,
};
console.log(formData);
};
return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}
// ✅ Better: use FormData API
function GoodForm() {
const handleSubmit = (e) => {
e.preventDefault();
const data = new FormData(e.target);
const formData = Object.fromEntries(data);
console.log(formData);
};
return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}Some events fire very often (scroll, resize, mouse move). Debouncing delays execution:
function SearchInput() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
// Simple debounce: only update after user stops typing
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
// Cancel previous timeout
clearTimeout(handleChange.timeoutId);
// Set new timeout
handleChange.timeoutId = setTimeout(() => {
console.log("Searching for:", value);
// Perform search here
}, 500);
};
return (
<div>
<input
value={query}
onChange={handleChange}
placeholder="Search (waits 500ms)..."
/>
</div>
);
}<details>
<summary>📚 More Examples</summary>
// Better debounce using a hook
import { useCallback } from "react";
function useDebounce(callback, delay) {
const timeoutId = useCallback(
(value) => {
const id = setTimeout(() => callback(value), delay);
return () => clearTimeout(id);
},
[callback, delay]
);
return timeoutId;
}
function SearchWithHook() {
const [query, setQuery] = useState("");
const debouncedSearch = useDebounce((value) => {
console.log("Searching for:", value);
}, 500);
const handleChange = (e) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
};
return <input onChange={handleChange} value={query} />;
}</details>
Attaching handlers to parent elements is more efficient than to each child:
// ❌ Less efficient: handler on each item
function ListBad() {
const items = Array.from({ length: 1000 }, (_, i) => i);
return (
<ul>
{items.map((item) => (
<li key={item} onClick={() => console.log("Item:", item)}>
Item {item}
</li>
))}
</ul>
);
}
// ✅ Better: single handler on parent
function ListGood() {
const items = Array.from({ length: 1000 }, (_, i) => i);
const handleListClick = (e) => {
const li = e.target.closest("li");
if (li) {
console.log("Item:", li.textContent);
}
};
return (
<ul onClick={handleListClick}>
{items.map((item) => (
<li key={item}>Item {item}</li>
))}
</ul>
);
}Not all state updates need to update the component:
function Counter() {
const [count, setCount] = useState(0);
// Track clicks externally without updating component
const clickCountRef = useRef(0);
const handleClick = () => {
clickCountRef.current += 1;
// Component doesn't re-render
if (clickCountRef.current % 5 === 0) {
setCount(count + 1); // Only update state every 5 clicks
}
};
return (
<div>
<p>Major count: {count}</p>
<p>Total clicks: {clickCountRef.current}</p>
<button onClick={handleClick}>Click</button>
</div>
);
}| Practice | Why | Example |
|---|---|---|
| Use event delegation | Reduces event listeners | One handler on parent for list items |
| Debounce high-frequency events | Prevents performance lag | Search input with 500ms delay |
| Avoid creating objects in handlers | Prevents unnecessary updates | Use FormData API instead |
| Use named functions for complex logic | Easier to debug | Extract handler logic to function |
| Use useCallback for child components | Prevents re-renders if child memoizes | useCallback with empty deps for stable identity |
function EfficientTodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Build app" },
]);
// Single handler for all actions using delegation
const handleListAction = (e) => {
const button = e.target.closest("button");
if (!button) return;
const id = parseInt(button.dataset.id);
const action = button.dataset.action;
if (action === "delete") {
setTodos(todos.filter((t) => t.id !== id));
}
};
// Search with debouncing
const [searchQuery, setSearchQuery] = useState("");
const handleSearch = (e) => {
const value = e.target.value;
setSearchQuery(value);
clearTimeout(handleSearch.timeoutId);
handleSearch.timeoutId = setTimeout(() => {
console.log("Filtering for:", value);
}, 300);
};
return (
<div>
<input
onChange={handleSearch}
placeholder="Search..."
/>
<ul onClick={handleListAction}>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button data-id={todo.id} data-action="delete">
Delete
</button>
</li>
))}
</ul>
</div>
);
}Ready to practice? Challenges | View All Topics
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward