Skip to main content

no-use-effect

Prevent unnecessary React `useEffect` usage by steering code toward derived state, event handlers, memoization, `key`-based resets, `useSyncExternalStore`, and framework or query-library data APIs. Use when writing or reviewing React/Next.js components, refactoring effect-heavy code, or when a change introduces `useEffect`, `useLayoutEffect`, or dependency-array churn. Triggers on: "avoid useEffect", "remove useEffect", "you might not need an effect", "effect dependencies", "derived state", "React side effects".

설치로 이동

소스 정보

저장소
caidanw/skills
최근 소스 활동
2026년 3월 17일 22:32
감지된 SKILL.md 언어
영어
스타
8
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
2 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
no-use-effect
description
Prevent unnecessary React `useEffect` usage by steering code toward derived state, event handlers, memoization, `key`-based resets, `useSyncExternalStore`, and framework or query-library data APIs. Use when writing or reviewing React/Next.js components, refactoring effect-heavy code, or when a change introduces `useEffect`, `useLayoutEffect`, or dependency-array churn. Triggers on: "avoid useEffect", "remove useEffect", "you might not need an effect", "effect dependencies", "derived state", "React side effects".
# No useEffect Treat raw `useEffect` as a last resort, not a default tool. Most component logic belongs in render, event handlers, framework data APIs, or narrower React primitives. This skill is based primarily on the React docs (`You Might Not Need an Effect`, `Synchronizing with Effects`, `Removing Effect Dependencies`), with supporting guidance from Dan Abramov, Kent C. Dodds, TkDodo, and team practices that ban or heavily restrict raw `useEffect`. ## Core Rule Before adding an Effect, answer this question: "What external system am I synchronizing with, and why can't this be done during render, in an event handler, or with a more specific primitive?" If you cannot name the external system, do not write the Effect. External systems include: - browser APIs and DOM subscriptions - timers, sockets, observers, media APIs - third-party widgets and imperative libraries - network synchronization that truly happens because the component is visible Not external systems: - derived values from props or state - event-driven actions like submit, save, buy, delete, notify - resetting local state because props changed - chaining state updates to trigger more state updates - passing data upward just because a child rendered ## Replacement Patterns ### 1. Derive during render Do not mirror props or state into more state. ```tsx // Bad const [fullName, setFullName] = useState(""); useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]); // Good const fullName = `${firstName} ${lastName}`; ``` Use this for filtered lists, labels, counts, booleans, selected objects, and other values that can be computed from current inputs. ### 2. Memoize expensive pure work If the calculation is slow, cache it with `useMemo`. Do not use an Effect that immediately calls `setState` with a computed value. ```tsx const visibleTodos = useMemo(() => getVisibleTodos(todos, filter), [todos, filter]); ``` First ask whether the work is actually expensive. If not, calculate it directly during render. ### 3. Put user actions in event handlers If the code runs because the user clicked, submitted, selected, dragged, or typed, keep it in the corresponding handler. ```tsx // Bad useEffect(() => { if (shouldSubmit) { void saveForm(form); } }, [shouldSubmit, form]); // Good async function handleSubmit() { await saveForm(form); } ``` Never build "set flag -> Effect notices flag -> Effect does the real work" flows unless the flag is synchronizing with a real external system. ### 4. Reset identity with `key` when you want a fresh instance If a component should become a brand-new instance when some identity changes, give it a different `key`. React will unmount the old subtree and mount a new one, which resets all local state below that point without an Effect. ```tsx // Bad function Profile({ userId }: { userId: string }) { const [comment, setComment] = useState(""); useEffect(() => { setComment(""); }, [userId]); return <input value={comment} onChange={(e) => setComment(e.target.value)} />; } // Good function ProfilePage({ userId }: { userId: string }) { return <Profile key={userId} userId={userId} />; } function Profile({ userId }: { userId: string }) { const [comment, setComment] = useState(""); return <input value={comment} onChange={(e) => setComment(e.target.value)} />; } ``` Use this when the identity really changed: user, document, chat room, wizard step, or form instance. Do not use it when you only want to tweak one field, because remounting resets the entire subtree. ### 5. Lift state or control the component If an Effect exists only to notify a parent after local state changed, the data model is usually wrong. Prefer lifting state up or updating both sides in the same event. ```tsx // Bad function Toggle({ onChange }: { onChange: (next: boolean) => void }) { const [isOn, setIsOn] = useState(false); useEffect(() => { onChange(isOn); }, [isOn, onChange]); return <button onClick={() => setIsOn(!isOn)}>{isOn ? "On" : "Off"}</button>; } // Good function Toggle({ isOn, onChange }: { isOn: boolean; onChange: (next: boolean) => void }) { return <button onClick={() => onChange(!isOn)}>{isOn ? "On" : "Off"}</button>; } ``` ### 6. Use `useSyncExternalStore` for external subscriptions Use `useSyncExternalStore` when the source of truth lives outside React and changes over time. This is React's built-in primitive for subscribing to external stores. Good fits: - browser APIs with a current value plus subscription events - third-party or app-level stores outside React state - reusable hooks like `useOnlineStatus()` or `useMediaQuery()` The shape is: ```tsx const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); ``` Where: - `subscribe` tells React how to listen for changes and return cleanup - `getSnapshot` reads the current value from the external store - `getServerSnapshot` provides the initial server and hydration value when SSR is involved ```tsx // Bad function useOnlineStatus() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { function updateStatus() { setIsOnline(navigator.onLine); } updateStatus(); window.addEventListener("online", updateStatus); window.addEventListener("offline", updateStatus); return () => { window.removeEventListener("online", updateStatus); window.removeEventListener("offline", updateStatus); }; }, []); return isOnline; } // Good const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true); ``` Prefer this over hand-rolled subscription Effects in components. Important caveats from the React docs: - `getSnapshot` must return a stable cached value unless the store actually changed - if `subscribe` changes identity every render, React will resubscribe every render - if you support SSR, `getServerSnapshot` must match between server render and hydration Do not use this for: - normal component state that should live in `useState`, `useReducer`, or context - derived values you can calculate during render - user actions handled by event handlers - one-time fetching - general side effects that are not subscriptions ### 7. Fetch through the framework or a data library Avoid raw fetch-in-Effect code in app components when a framework loader or query library is available. Preferred order: 1. framework data APIs (`Next.js`, `React Router`, Remix, server components, route loaders) 2. data libraries (`TanStack Query`, `SWR`) 3. a focused custom hook 4. raw `useEffect` only if none of the above fit If you must fetch in an Effect, handle cleanup so stale responses are ignored. ```tsx // Bad useEffect(() => { void fetchResults(query).then(setResults); }, [query]); // Good useEffect(() => { let ignore = false; void fetchResults(query).then((next) => { if (!ignore) { setResults(next); } }); return () => { ignore = true; }; }, [query]); ``` ### 8. Encapsulate unavoidable imperative synchronization Sometimes an Effect is correct: sockets, observers, widgets, media controls, focus management, layout measurement, or DOM APIs that must stay in sync with rendered output. When that happens: - keep the Effect tiny and specific - name the external system in a comment or hook name - include full cleanup - prefer a custom hook over open-coded Effect logic in many components - consider `useLayoutEffect` only for pre-paint measurement or visual correctness ```tsx // Bad function ChatRoom({ roomId }: { roomId: string }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); }, [roomId]); } // Good function useChatConnection(roomId: string) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => { connection.disconnect(); }; }, [roomId]); } ``` If your team bans raw `useEffect`, put these escape hatches behind approved hooks instead of sprinkling Effects through feature code. ## Anti-Patterns Treat these as red flags during authoring or review: - `useEffect(() => setX(...), [...])` for derived state - Effect only exists to call a function after a button click - effect chains where one state update triggers another Effect and another render - dependency arrays fought with `eslint-disable`, refs-as-flags, or intentionally stale closures - object or function dependencies recreated every render without need - child component fetching data and pushing it upward in an Effect - mount-only initialization that should live in module scope, entrypoints, or framework boot code ## Review Checklist Before accepting a new Effect, verify all of these: - [ ] The code synchronizes with a real external system - [ ] Derived render data, memoization, events, `key`, lifted state, and `useSyncExternalStore` were all considered first - [ ] The dependency list is honest and complete - [ ] Setup and cleanup are symmetrical - [ ] The Effect is idempotent under Strict Mode re-mounting - [ ] Data fetching is not bypassing an existing framework or query abstraction - [ ] The logic is small enough that a custom hook or helper name explains its purpose ## Team Policy For teams trying to ban or heavily restrict raw `useEffect`, use this policy: 1. Do not introduce `useEffect` for derived state, events, or resets. 2. Prefer render-time derivation, event handlers, `key`, `useMemo`, and `useSyncExternalStore`. 3. Route data loading through the framework or an approved query library. 4. If an Effect is still necessary, hide it behind a narrowly named custom hook or document the external system and cleanup explicitly in review. 5. Never silence `react-hooks/exhaustive-deps` to "make it work". ## References For source material and further reading, see `references/sources.md`.
GitHub에서 보기