Subscribe to and depend on only the values you use
Subscribe to the narrowest value a component renders, and give Effects the narrowest dependencies they read. When a value is needed only inside an event handler, read it there instead of subscribing to it.
Implementation
- Subscribe to a derived value when the component only needs the derivation, such as a media query match instead of the exact window width.
- Select only the needed fields from a store, rather than the whole store object.
- When a value is read only in a handler, such as a search parameter used when sharing a link, read it in the handler. In a router-based app, read the current URL at that moment rather than subscribing through a Hook that re-renders on every change.
- Use primitive fields, such as
user.id, as Effect dependencies when the Effect reads only those fields. - Compute a boolean before the Effect, such as
const isMobile = width < 768, when the Effect cares only about crossing a threshold.
Rationale
A component re-renders whenever a value it subscribes to changes, and an Effect re-runs whenever a dependency changes. Subscribing to a continuous value such as window width re-renders on every pixel of a resize, even when the output changes only at one breakpoint. Narrowing the subscription or dependency limits the work to changes that affect the result.
Examples
Application: A derived subscription
Incorrect (counterexample):
function Sidebar() {
const width = useWindowWidth();
const isMobile = width < 768;
return <nav className={isMobile ? 'mobile' : 'desktop'} />;
}
The sidebar re-renders on every resize event.
Correct:
function Sidebar() {
const isMobile = useMediaQuery('(max-width: 767px)');
return <nav className={isMobile ? 'mobile' : 'desktop'} />;
}
Application: A value read only in a handler
Incorrect (counterexample):
function ShareButton({ chatId }: { chatId: string }) {
const searchParams = useSearchParams();
function handleShare() {
shareChat(chatId, { ref: searchParams.get('ref') });
}
return <button onClick={handleShare}>Share</button>;
}
The button re-renders on every search parameter change, although it renders nothing that depends on them.
Correct:
function ShareButton({ chatId }: { chatId: string }) {
function handleShare() {
const ref = new URLSearchParams(window.location.search).get('ref');
shareChat(chatId, { ref });
}
return <button onClick={handleShare}>Share</button>;
}
Application: Effect dependencies
Incorrect (counterexample):
useEffect(() => {
trackProfileView(user.id);
}, [user]);
The Effect re-runs whenever any field of user changes.
Correct:
useEffect(() => {
trackProfileView(user.id);
}, [user.id]);
Validation
Use the React DevTools Profiler to check that components re-render only when values they display change. Check that Effects list the specific fields they read, and that the Hooks lint rules pass.
Subscribing to a value the component renders directly is not a violation, however often it changes.