React is fast, but unnecessary re-renders can slow you down. Here’s how to stop them.
🔹 React.memo --- Component Caching
React.memo is a higher-order component. It prevents a component from re-rendering if its props haven't changed.
jsx
// Without memo: re-renders every time parent re-renders.
const ExpensiveComponent = ({ data }) => {
console.log("Rendering!");
return <div>{data}</div>;
};
// With memo: only re-renders if 'data' changes.
const MemoizedComponent = React.memo(ExpensiveComponent);
Warning: Memoization isn't free. Use it only for components that re-render often with the same props.
---
🔹 useCallback --- Memoizing Functions
When you pass a function as a prop, it gets re-created on every render. This breaks React.memo because the prop looks different every time.
jsx
// ❌ Bad: Creates a new function every render
function Parent() {
const handleClick = () => console.log("clicked");
return <Child onClick={handleClick} />;
}
// ✅ Good: useCallback caches the function
function Parent() {
const handleClick = useCallback(() => {
console.log("clicked");
}, []); // Empty array = never changes
return <Child onClick={handleClick} />;
}
Analogy: useCallback is like giving someone a permanent business card. Without it, you hand them a new one every time they see you (pointless).
---
🔹 useTransition --- Non-Urgent Updates
Sometimes state updates cause lag (e.g., filtering a giant list while typing). useTransition lets you mark certain updates as "low priority" so they don't block the UI.
jsx
import { useState, useTransition } from "react";
function SearchPage() {
const [query, setQuery] = useState("");
const [filteredList, setFilteredList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // Urgent update (typing)
startTransition(() => {
// Low priority update (filtering large array)
const results = hugeList.filter(item => item.includes(value));
setFilteredList(results);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading results...</p>}
</div>
);
}
---
Part 3 --- DOM & UI Mastery
🚪 Portals --- Rendering Outside the Parent
Sometimes you need to render something outside the root div (e.g., modals, tooltips, dropdowns) to avoid CSS clipping or z-index issues.
ReactDOM.createPortal lets you render a component anywhere in the DOM.
jsx
import { createPortal } from "react-dom";
function Modal({ children, isOpen }) {
if (!isOpen) return null;
// Render this modal inside the "modal-root" div instead of the parent tree
return createPortal(
<div className="modal-overlay">
<div className="modal">{children}</div>
</div>,
document.getElementById("modal-root") // Must exist in index.html
);
}
Analogy: Portals are like walkie-talkies. Even though you're in one room (parent component), you broadcast your message (modal UI) to another room (modal-root) seamlessly.
---
🪞 forwardRef & useImperativeHandle --- Advanced Refs
Sometimes you need to access a DOM element inside a child component. forwardRef lets the parent pass a ref down.
jsx
// Child component
const FancyInput = forwardRef((props, ref) => {
return <input ref={ref} className="fancy" {...props} />;
});
// Parent component
function Parent() {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus(); // Focusing the child's input
}, []);
return <FancyInput ref={inputRef} />;
}
useImperativeHandle lets you limit what the parent can do with the ref (like exposing only focus and clear).
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ""; }
}));
return <input ref={inputRef} />;
});