Navigate
Search topics, hooks, and fundamentals
Inline objects and functions create new references every render. Same values, but React thinks they changed.
In JavaScript, { color: "red" } !== { color: "red" }. Two objects with identical values are different objects in memory. React uses === to compare dependencies, so it sees a "change" every render.
function MyComponent() {
// New object created every render!
const style = { color: "red", fontSize: 16 };
useEffect(() => {
console.log("style changed!"); // runs EVERY render 😱
}, [style]); // style is a new reference each time
return <div style={style}>Hello</div>;
}Click the button to force a re-render. The object values are identical every time, but === comparison returns false because it's a new object in memory.
Wrap inline objects in useMemo to keep the same reference across renders:
function MyComponent() {
// Same object reference across renders
const style = useMemo(
() => ({ color: "red", fontSize: 16 }),
[] // no deps = stable forever
);
useEffect(() => {
console.log("style changed!"); // runs only once ✅
}, [style]);
return <div style={style}>Hello</div>;
}The same trap applies to functions. A function defined inside the component is re-created every render:
function Parent() {
const [count, setCount] = useState(0);
// New function reference every render!
const handleClick = () => console.log("clicked");
// MemoChild re-renders anyway because handleClick is "new"
return <MemoChild onClick={handleClick} />;
}Use useCallback to keep a stable function reference:
function Parent() {
const [count, setCount] = useState(0);
// Stable function reference across renders
const handleClick = useCallback(
() => console.log("clicked"),
[]
);
// MemoChild correctly skips re-render ✅
return <MemoChild onClick={handleClick} />;
}Default parameter values with = {} create new references too. Hoist them outside the component:
Inline Default
style = {}child renders: 1Rendered once
Hoisted Default
style = DEFAULT_STYLEchild renders: 1Rendered once — skips re-renders!
Green = rendered once. Red = re-rendered needlessly.
Neither side receives an explicit style prop — both rely on their default parameter.
// Inline default: new {} every render
function Card({ style = {} }) { ... }
// memo'd child re-renders every time!
// Hoisted: same reference forever
const DEFAULT_STYLE = {};
function Card({ style = DEFAULT_STYLE }) { ... }
// memo'd child correctly skips re-render ✓The rule of thumb:
useMemouseCallback