skills/no-unnecessary-effects/SKILL.md
Before writing useEffect, run through a decision tree to verify it's actually needed. Prevents the most common React anti-pattern in AI-generated code.
npx skillsauth add Cst2989/react-tips-skill no-unnecessary-effectsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Every time you are about to write a useEffect, stop and answer this question:
Is this syncing with an external system?
External systems: WebSocket, browser APIs (IntersectionObserver, navigator.onLine), third-party libraries (map SDKs, chart widgets), DOM measurements, setInterval timers.
NOT external systems: props, state, values derived from props or state, user events (clicks, form submissions).
If the answer is no, do NOT write the effect. Use the decision tree below to find the right alternative.
Before writing the effect, check each case in order:
Compute it inline. No state, no effect.
// NEVER do this
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(data.filter(item => item.active));
}, [data]);
// DO this
const filtered = data.filter(item => item.active);
// If genuinely expensive and not using React Compiler:
const filtered = useMemo(() => data.filter(item => item.active), [data]);
Put the logic in the event handler. Effects respond to renders, not to user actions.
// NEVER do this
useEffect(() => {
if (submitted) {
performSearch(query);
setSubmitted(false);
}
}, [submitted, query]);
// DO this
function handleSubmit(e: FormEvent) {
e.preventDefault();
performSearch(query);
}
Use the key prop to let React unmount and remount the component with fresh state.
// NEVER do this
useEffect(() => {
setComment('');
}, [userId]);
// DO this — in the parent
<UserProfile key={userId} userId={userId} />
Use TanStack Query (React Query) or a similar library. If you must use useEffect, always add cleanup to ignore stale responses.
// Preferred
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// If useEffect is unavoidable, always handle race conditions
useEffect(() => {
let ignore = false;
fetchUser(userId).then(data => {
if (!ignore) setUser(data);
});
return () => { ignore = true; };
}, [userId]);
Call the parent's callback directly in the event handler, alongside setState. React batches both updates into one render.
// NEVER do this
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
// DO this
function handleClick() {
const next = !isOn;
setIsOn(next);
onChange(next);
}
Move the cascade into a single event handler. Derive what you can during render.
// NEVER do this — three effects, three render passes
useEffect(() => { setCity(''); }, [country]);
useEffect(() => { setDistrict(''); }, [city]);
useEffect(() => { setShippingCost(calculate(country, city, district)); }, [country, city, district]);
// DO this
function handleCountryChange(newCountry: string) {
setCountry(newCountry);
setCity('');
setDistrict('');
}
const shippingCost = country && city && district
? calculateShipping(country, city, district)
: 0;
Use useSyncExternalStore instead of manually wiring up listeners with useEffect.
// NEVER do this
useEffect(() => {
const handler = () => setIsOnline(navigator.onLine);
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
return () => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
};
}, []);
// DO this
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
If none of the above cases apply and the answer to "Is this an external system?" is genuinely yes, then useEffect is the right tool. Examples:
useLayoutEffect for pre-paint, useEffect for post-paint)IntersectionObserver, ResizeObserver)When writing a valid effect:
useEffect(function connectToChat() { ... })useLayoutEffect when measuring the DOM to avoid visual flickeruseEffect(() => setSomething(derivedValue), [dep]) — compute it inlineuseEffect to respond to click/submit/change eventsuseEffect to reset state on prop change without first considering the key propdevelopment
10 high-impact React patterns and anti-patterns - state management, performance, hooks, and component design. Use when writing or reviewing React components.
tools
Use when writing or reviewing JavaScript - prefer ES2025/ES2026 APIs (Iterator helpers, Set methods, Temporal, using, Promise.try, Error.isError, Math.sumPrecise, Map.getOrInsert) over older patterns.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.