Name useEffect Functions
Use named function declarations instead of arrow functions in useEffect. Also name cleanup functions.
Why
- Stack traces: Named functions appear in error stack traces, making debugging easier
- Self-documentation: The function name explains what the effect does
- Single responsibility: Naming encourages one concern per effect
- Code review: Easier to understand effect purpose at a glance
Bad: Anonymous Arrow Functions
// Bad: anonymous functions hide intent
useEffect(() => {
document.title = title;
}, [title]);
useEffect(() => {
let handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [onClose]);When these effects error, the stack trace shows anonymous or <anonymous>.
Good: Named Function Declarations
// Good: named functions are self-documenting
useEffect(
function syncDocumentTitle() {
document.title = title;
},
[title],
);
useEffect(
function handleEscapeKey() {
let handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", handler);
return function removeEscapeKeyHandler() {
window.removeEventListener("keydown", handler);
};
},
[onClose],
);Stack traces now show syncDocumentTitle or handleEscapeKey.
Pattern Examples
Data Synchronization
useEffect(
function syncLocalStorage() {
localStorage.setItem("preferences", JSON.stringify(preferences));
},
[preferences],
);Subscriptions
useEffect(function subscribeToOnlineStatus() {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return function unsubscribeFromOnlineStatus() {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);Form Reset (Remix pattern)
useEffect(
function resetFormOnSuccess() {
if (fetcher.state === "idle" && fetcher.data?.ok) {
formRef.current?.reset();
}
},
[fetcher.state, fetcher.data],
);Third-Party Integration
useEffect(function initializeMap() {
if (!mapRef.current) return;
let map = new MapLibrary(mapRef.current, { center });
mapInstanceRef.current = map;
return function destroyMap() {
map.destroy();
};
}, []);Analytics
useEffect(
function trackPageView() {
analytics.page(pathname);
},
[pathname],
);Naming Conventions
| Effect Purpose | Name Pattern |
|---|---|
| Sync data | sync[What] - syncDocumentTitle, syncLocalStorage |
| Subscribe | subscribeTo[What] - subscribeToOnlineStatus |
| Initialize | initialize[What] - initializeMap, initializeChart |
| Handle event | handle[What] - handleEscapeKey, handleResize |
| Track/log | track[What] - trackPageView, logError |
| Reset | reset[What] - resetFormOnSuccess |
| Cleanup | destroy[What], remove[What], unsubscribeFrom[What] |
Multiple Effects
Named functions make it clear why you have separate effects:
function UserProfile({ userId }: Props) {
useEffect(
function fetchUserData() {
// Fetch user when userId changes
},
[userId],
);
useEffect(
function trackProfileView() {
// Analytics - separate concern
analytics.track("profile_viewed", { userId });
},
[userId],
);
useEffect(function setupKeyboardShortcuts() {
// Keyboard handling - separate concern
}, []);
}Rules
- Always use named function declarations in
useEffect, not arrow functions - Name cleanup functions too (
return function cleanup() { ... }) - Use descriptive names that explain the effect's purpose
- One concern per effect - if you can't name it clearly, split it
- Follow naming conventions:
sync*,subscribeTo*,initialize*,handle*,track*