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 从公开仓库中收录这些内容。

举报或申请下架