No Use Effect

Factory-AI/factory-plugins/plugins/typescript/skills/no-use-effect

作者 Factory-AI7166a0799e3dd39184068160b9caaa1e6469028a無授權條款111 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫昨天更新

Enforce the no-useEffect rule when writing or reviewing React code. ACTIVATE when writing React components, refactoring existing useEffect calls, reviewing PRs with useEffect, or when an agent adds useEffect "just in case." Provides the five replacement patterns and the useMountEffect escape hatch.

AI 產生的概覽

在 React 程式碼中落實禁用 useEffect 的規則,提供五種替代模式與 useMountEffect 例外方案。

功能
此技能引導代理在撰寫或審查 React 元件時避免直接呼叫 useEffect。它會判斷 effect 的用途,並對應到五種替代模式之一:內聯衍生狀態、資料擷取函式庫、事件處理常式、用於一次性外部同步的 useMountEffect,以及以 key 為基礎的重新掛載。它也說明元件結構慣例,以及 lint、型別檢查與測試的驗證指令。
適用情境
適用於撰寫新的 React 元件、重構現有 useEffect 呼叫、審查引入 useEffect 的提取要求,或代理防禦性地加入 useEffect 時。它面向希望禁止直接使用 useEffect 的 React 程式庫。
執行需求
僅為說明內容,不隨附指令碼。它引用 lint 規則設定、useQuery 等 React 資料擷取函式庫,以及專案的 lint、型別檢查與測試指令。

No useEffect

Never call useEffect directly. Use derived state, event handlers, data-fetching libraries, or useMountEffect instead.

Quick Reference

Instead of useEffect for...Use
Deriving state from other state/propsInline computation (Rule 1)
Fetching datauseQuery / data-fetching library (Rule 2)
Responding to user actionsEvent handlers (Rule 3)
One-time external sync on mountuseMountEffect (Rule 4)
Resetting state when a prop changeskey prop on parent (Rule 5)

When to Use This Skill

  • Writing new React components
  • Refactoring existing useEffect calls
  • Reviewing PRs that introduce useEffect
  • An agent adds useEffect "just in case"

Workflow

1. Identify the useEffect

Determine what the effect is doing -- deriving state, fetching data, responding to an event, syncing with an external system, or resetting state.

2. Apply the Correct Replacement Pattern

Use the five rules below to pick the right replacement.

3. Verify

npm run lint -- --filter=<package>npm run typecheck -- --filter=<package>npm run test -- --filter=<package>

The Escape Hatch: useMountEffect

For the rare case where you need to sync with an external system on mount:

The implementation wraps useEffect with an empty dependency array to make intent explicit:

export function useMountEffect(effect: () => void | (() => void)) {  /* eslint-disable no-restricted-syntax */  useEffect(effect, []);}

Replacement Patterns

Rule 1: Derive state, do not sync it

Most effects that set state from other state are unnecessary and add extra renders.

// BAD: Two render cycles - first stale, then filteredfunction ProductList() {  const [products, setProducts] = useState([]);  const [filteredProducts, setFilteredProducts] = useState([]);
  useEffect(() => {    setFilteredProducts(products.filter((p) => p.inStock));  }, [products]);}
// GOOD: Compute inline in one renderfunction ProductList() {  const [products, setProducts] = useState([]);  const filteredProducts = products.filter((p) => p.inStock);}

Smell test: You are about to write useEffect(() => setX(deriveFromY(y)), [y]), or you have state that only mirrors other state or props.

Rule 2: Use data-fetching libraries

Effect-based fetching creates race conditions and duplicated caching logic.

// BAD: Race condition riskfunction ProductPage({ productId }) {  const [product, setProduct] = useState(null);
  useEffect(() => {    fetchProduct(productId).then(setProduct);  }, [productId]);}
// GOOD: Query library handles cancellation/caching/stalenessfunction ProductPage({ productId }) {  const { data: product } = useQuery(['product', productId], () =>    fetchProduct(productId)  );}

Smell test: Your effect does fetch(...) and then setState(...), or you are re-implementing caching, retries, cancellation, or stale handling.

Rule 3: Event handlers, not effects

If a user clicks a button, do the work in the handler.

// BAD: Effect as an action relayfunction LikeButton() {  const [liked, setLiked] = useState(false);
  useEffect(() => {    if (liked) {      postLike();      setLiked(false);    }  }, [liked]);
  return <button onClick={() => setLiked(true)}>Like</button>;}
// GOOD: Direct event-driven actionfunction LikeButton() {  return <button onClick={() => postLike()}>Like</button>;}

Smell test: State is used as a flag so an effect can do the real action, or you are building "set flag -> effect runs -> reset flag" mechanics.

Rule 4: useMountEffect for one-time external sync

Good uses: DOM integration (focus, scroll), third-party widget lifecycles, browser API subscriptions.

// BAD: Guard inside effectfunction VideoPlayer({ isLoading }) {  useEffect(() => {    if (!isLoading) playVideo();  }, [isLoading]);}
// GOOD: Mount only when preconditions are metfunction VideoPlayerWrapper({ isLoading }) {  if (isLoading) return <LoadingScreen />;  return <VideoPlayer />;}
function VideoPlayer() {  useMountEffect(() => playVideo());}

Use useMountEffect for stable dependencies (singletons, refs, context values that never change):

// BAD: useEffect with dependency that never changesuseEffect(() => {  connectionManager.on('connected', handleConnect);  return () => connectionManager.off('connected', handleConnect);}, [connectionManager]); // connectionManager is a singleton from context
// GOOD: useMountEffect for stable dependencies
useMountEffect(() => {  connectionManager.on('connected', handleConnect);  return () => connectionManager.off('connected', handleConnect);});

Smell test: You are synchronizing with an external system, and the behavior is naturally "setup on mount, cleanup on unmount."

Rule 5: Reset with key, not dependency choreography

// BAD: Effect attempts to emulate remount behaviorfunction VideoPlayer({ videoId }) {  useEffect(() => {    loadVideo(videoId);  }, [videoId]);}
// GOOD: key forces clean remountfunction VideoPlayer({ videoId }) {  useMountEffect(() => {    loadVideo(videoId);  });}
function VideoPlayerWrapper({ videoId }) {  return <VideoPlayer key={videoId} videoId={videoId} />;}

Smell test: You are writing an effect whose only job is to reset local state when an ID/prop changes, or you want the component to behave like a brand-new instance for each entity.

Component Structure Convention

Computed values come after hooks and local state, never via useEffect:

export function FeatureComponent({ featureId }: ComponentProps) {  // Hooks first  const { data, isLoading } = useQueryFeature(featureId);
  // Local state  const [isOpen, setIsOpen] = useState(false);
  // Computed values (NOT useEffect + setState)  const displayName = user?.name ?? 'Unknown';
  // Event handlers  const handleClick = () => { setIsOpen(true); };
  // Early returns  if (isLoading) return <Loading />;
  // Render  return <Flex direction="column" gap="lg">...</Flex>;}

來源與署名

來源:Factory-AI/factory-plugins位於plugins/typescript/skills/no-use-effect提交7166a07

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架