react-useeffect-avoid

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React: When Not to Use useEffect

React:何时不应使用useEffect

Core Principle

核心原则

useEffect
is an escape hatch for synchronizing with external systems, not a general-purpose tool for state management or event handling.
Modern React patterns prioritize one-way data flow and event-driven updates over effect-based synchronization to avoid performance penalties and complex synchronization bugs.

useEffect
是用于与外部系统同步的“逃生舱”,而非状态管理或事件处理的通用工具。
现代React模式优先采用单向数据流事件驱动更新,而非基于effect的同步方式,以此避免性能损耗和复杂的同步bug。

When NOT to Use useEffect

何时不应使用useEffect

1. ❌ Calculating Derived State

1. ❌ 计算派生状态

Problem: Computing one state value from another using
useEffect
causes unnecessary double-renders and complexity.
jsx
// ❌ BAD: Double render cycle
function FilteredList({ items }) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState([]);

  useEffect(() => {
    setFiltered(items.filter(item => item.name.includes(query)));
  }, [items, query]); // Renders twice on every input

  return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
✅ Solution: Calculate derived state directly during render.
jsx
// ✅ GOOD: Single render, no effect needed
function FilteredList({ items }) {
  const [query, setQuery] = useState('');

  // Calculated every render - no state, no effect
  const filtered = items.filter(item => item.name.includes(query));

  return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
Why it's better:
  • Immediate feedback UI updates (no double render pause)
  • Simpler mental model: what you see is what you get
  • No dependency array to manage
  • React can optimize calculations via memoization when needed

问题: 使用
useEffect
从另一个状态值计算新状态会导致不必要的双重渲染和复杂度提升。
jsx
// ❌ 不良写法:触发两次渲染周期
function FilteredList({ items }) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState([]);

  useEffect(() => {
    setFiltered(items.filter(item => item.name.includes(query)));
  }, [items, query]); // 每次输入都会触发两次渲染

  return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
✅ 解决方案: 在渲染阶段直接计算派生状态。
jsx
// ✅ 推荐写法:单次渲染,无需effect
function FilteredList({ items }) {
  const [query, setQuery] = useState('');

  // 每次渲染时计算 - 无需额外状态,无需effect
  const filtered = items.filter(item => item.name.includes(query));

  return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
优势:
  • UI更新即时反馈(无双重渲染延迟)
  • 心智模型更简单:所见即所得
  • 无需管理依赖数组
  • 必要时React可通过memoization优化计算

2. ❌ Resetting State When a Prop Changes

2. ❌ 当Props变化时重置状态

Problem: Watching a prop change and manually resetting state causes stale data display and extra renders.
jsx
// ❌ BAD: Shows old state briefly, then new
function UserForm({ userId }) {
  const [userName, setUserName] = useState('');
  const [email, setEmail] = useState('');

  useEffect(() => {
    setUserName('');  // First render shows old userName
    setEmail('');    // Then this effect runs to reset
  }, [userId]);      // Double render every userId change

  return (
    <form>
      <input value={userName} onChange={e => setUserName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}
✅ Solution A: Key Prop (Preferred)
jsx
// ✅ GOOD: React tears down and rebuilds component
function App() {
  const [userId, setUserId] = useState(1);

  return (
    <div>
      <button onClick={() => setUserId(prev => prev + 1)}>Next User</button>
      {/* Different key = different component instance */}
      <UserForm key={userId} userId={userId} />
    </div>
  );
}
✅ Solution B: Derived State (When you only need partial reset)
jsx
// ✅ GOOD: Only reset specific values, keep others
function UserForm({ userId }) {
  const [userName, setUserName] = useSyncExternalStore(
    () => ({ onSet: setUserName }), // Reset when userId changes
    { value: '' }
  );
  const [email, setEmail] = useState('');

  // Email persists, userName resets
  return <form>...</form>;
}
⚠️ Caveat: The
key
prop approach remounts the entire component. Use when you need a complete state reset. For partial resets, consider derived state patterns.

问题: 监听Props变化并手动重置状态会导致旧数据短暂显示和额外渲染。
jsx
// ❌ 不良写法:先显示旧状态,再更新为新状态
function UserForm({ userId }) {
  const [userName, setUserName] = useState('');
  const [email, setEmail] = useState('');

  useEffect(() => {
    setUserName('');  // 首次渲染显示旧userName
    setEmail('');    // 随后effect执行重置操作
  }, [userId]);      // 每次userId变化都会触发两次渲染

  return (
    <form>
      <input value={userName} onChange={e => setUserName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}
✅ 解决方案A:使用Key Prop(优先推荐)
jsx
// ✅ 推荐写法:React会卸载并重建组件
function App() {
  const [userId, setUserId] = useState(1);

  return (
    <div>
      <button onClick={() => setUserId(prev => prev + 1)}>下一个用户</button>
      {/* 不同的key对应不同的组件实例 */}
      <UserForm key={userId} userId={userId} />
    </div>
  );
}
✅ 解决方案B:派生状态(仅需部分重置时)
jsx
// ✅ 推荐写法:仅重置特定值,保留其他状态
function UserForm({ userId }) {
  const [userName, setUserName] = useSyncExternalStore(
    () => ({ onSet: setUserName }), // 当userId变化时重置
    { value: '' }
  );
  const [email, setEmail] = useState('');

  // Email状态保留,userName状态重置
  return <form>...</form>;
}
⚠️ 注意: 使用key prop的方式会重新挂载整个组件。当你需要完全重置状态时使用该方法。若仅需部分重置,可考虑派生状态模式。

3. ❌ Waterfall of State Updates

3. ❌ 状态更新瀑布流

Problem: Multiple effects triggering each other create cascading renders that are hard to debug.
jsx
// ❌ BAD: Effects trigger effects
function OrderForm() {
  const [formData, setFormData] = useState({});
  const [validated, setValidated] = useState(false);
  const [error, setError] = useState(null);

  // Effect 1: Validate when formData changes
  useEffect(() => {
    setValidated(validateData(formData));
  }, [formData]);

  // Effect 2: Set error when validation changes
  useEffect(() => {
    setError(validated ? null : 'Invalid');
  }, [validated]);

  // Effect 3: Submit when no error
  useEffect(() => {
    if (!error && validated) submitOrder(formData);
  }, [error, validated, formData]);
}
✅ Solution: Handle all logic in event handler.
jsx
// ✅ GOOD: One handler, one render, clear flow
function OrderForm() {
  const [formData, setFormData] = useState({});

  const handleSubmit = () => {
    // All logic happens atomically
    if (validateData(formData)) {
      submitOrder(formData);  // No intermediate states
    } else {
      setError('Invalid form');  // Set directly, no cascade
    }
  };

  return <button onClick={handleSubmit}>Submit</button>;
}

问题: 多个effect互相触发会导致级联渲染,难以调试。
jsx
// ❌ 不良写法:Effect互相触发
function OrderForm() {
  const [formData, setFormData] = useState({});
  const [validated, setValidated] = useState(false);
  const [error, setError] = useState(null);

  // Effect 1:当formData变化时验证
  useEffect(() => {
    setValidated(validateData(formData));
  }, [formData]);

  // Effect 2:当验证状态变化时设置错误信息
  useEffect(() => {
    setError(validated ? null : 'Invalid');
  }, [validated]);

  // Effect 3:无错误时提交表单
  useEffect(() => {
    if (!error && validated) submitOrder(formData);
  }, [error, validated, formData]);
}
✅ 解决方案: 在事件处理器中处理所有逻辑。
jsx
// ✅ 推荐写法:单个处理器,单次渲染,流程清晰
function OrderForm() {
  const [formData, setFormData] = useState({});

  const handleSubmit = () => {
    // 所有逻辑原子性执行
    if (validateData(formData)) {
      submitOrder(formData);  // 无中间状态
    } else {
      setError('Invalid form');  // 直接设置,无级联
    }
  };

  return <button onClick={handleSubmit}>Submit</button>;
}

4. ❌ Fetching Data on User Action

4. ❌ 用户操作触发的数据请求

Problem: Using a flag to trigger effects loses user intent and creates fragile code.
jsx
// ❌ BAD: Intent is lost, hard to follow
function LoginForm() {
  const [username, setUsername] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = () => {
    setSubmitted(true);  // Just a flag, no actual logic
  };

  useEffect(() => {
    if (submitted) {
      login(username);  // What triggered this? Which submit was it?
    }
  }, [submitted, username]);
}
✅ Solution: Perform action directly in handler.
jsx
// ✅ GOOD: Clear intent, direct action
function LoginForm() {
  const [username, setUsername] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();  // User action context preserved

    // Actual logic here, not delayed
    try {
      await login(username);
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

问题: 使用标记位触发effect会丢失用户意图,代码脆弱。
jsx
// ❌ 不良写法:意图不清晰,难以追踪
function LoginForm() {
  const [username, setUsername] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = () => {
    setSubmitted(true);  // 仅设置标记位,无实际逻辑
  };

  useEffect(() => {
    if (submitted) {
      login(username);  // 是什么触发了这个操作?是哪次提交?
    }
  }, [submitted, username]);
}
✅ 解决方案: 在处理器中直接执行操作。
jsx
// ✅ 推荐写法:意图清晰,直接执行
function LoginForm() {
  const [username, setUsername] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();  // 保留用户操作上下文

    // 实际逻辑直接在此执行,无延迟
    try {
      await login(username);
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

5. ❌ Filtering/Transforming Lists

5. ❌ 过滤/转换列表

Problem: Creating filtered lists via effects is a common mistake that causes extra renders.
jsx
// ❌ BAD: Unnecessary effect
function ProductList({ products }) {
  const [visibleProducts, setVisibleProducts] = useState([]);

  useEffect(() => {
    setVisibleProducts(products.filter(p => p.inStock));
  }, [products]); // Runs twice whenever products change
}
✅ Solution: Filter directly during render.
jsx
// ✅ GOOD: Immediate, single render
function ProductList({ products }) {
  const visibleProducts = products.filter(p => p.inStock);
}

问题: 通过effect创建过滤列表是常见错误,会导致额外渲染。
jsx
// ❌ 不良写法:不必要的effect
function ProductList({ products }) {
  const [visibleProducts, setVisibleProducts] = useState([]);

  useEffect(() => {
    setVisibleProducts(products.filter(p => p.inStock));
  }, [products]); // 每次products变化时执行两次
}
✅ 解决方案: 在渲染阶段直接过滤。
jsx
// ✅ 推荐写法:即时执行,单次渲染
function ProductList({ products }) {
  const visibleProducts = products.filter(p => p.inStock);
}

6. ❌ Subscribing to Browser APIs (Without useSyncExternalStore)

6. ❌ 订阅浏览器API(未使用useSyncExternalStore)

Problem: Using
useState
+
useEffect
for external subscriptions causes "tearing" in concurrent rendering.
jsx
// ❌ BAD: UI can show inconsistent state during concurrent updates
function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
}
✅ Solution: Use
useSyncExternalStore
for external state.
jsx
// ✅ GOOD: Prevents tearing, single source of truth
function WindowSize() {
  const width = useSyncExternalStore(
    // Subscribe - return cleanup function
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    // Get snapshot
    () => window.innerWidth,
    // Server fallback
    () => 1200
  );
}
Why it's better:
  • React can guarantee UI consistency even during concurrent rendering
  • Single source of truth
  • Built-in optimization - subscription fires only when needed

问题: 使用
useState
+
useEffect
处理外部订阅会在并发渲染中导致“撕裂”问题。
jsx
// ❌ 不良写法:并发更新期间UI可能显示不一致状态
function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
}
✅ 解决方案: 使用
useSyncExternalStore
处理外部状态。
jsx
// ✅ 推荐写法:防止撕裂,单一数据源
function WindowSize() {
  const width = useSyncExternalStore(
    // 订阅 - 返回清理函数
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    // 获取快照
    () => window.innerWidth,
    // 服务端回退值
    () => 1200
  );
}
优势:
  • 即使在并发渲染期间,React也能保证UI一致性
  • 单一数据源
  • 内置优化 - 仅在需要时触发订阅

7. ❌ Initial Data Fetch Without Dependencies (Stale Data)

7. ❌ 无依赖的初始数据请求(数据过期)

Problem: Empty dependency array with state inside effect causes stale bugs.
jsx
// ❌ BAD: Id never updates, always fetches user 1
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/user/1`).then(res => res.json()).then(setUser);
  }, [ ]); // Missing userId dependency!
}
✅ Solution: Include all dependencies or use proper patterns.
jsx
// ✅ GOOD: Correct dependencies
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/user/${userId}`).then(res => res.json()).then(setUser);
  }, [userId]); // Properly tracks changes
}
Better: Move to event-driven fetching or data libraries.
jsx
// ✅ EVEN BETTER: No effect, just loader
function UserProfile({ userId }) {
  const user = use(userId); // React 19's use API
  // Or use React Query, SWR, etc.
}

问题: 依赖数组为空且effect内部使用状态会导致过期bug。
jsx
// ❌ 不良写法:Id从不更新,始终请求用户1的数据
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/user/1`).then(res => res.json()).then(setUser);
  }, [ ]); // 缺少userId依赖!
}
✅ 解决方案: 包含所有依赖或使用合适的模式。
jsx
// ✅ 推荐写法:正确的依赖项
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/user/${userId}`).then(res => res.json()).then(setUser);
  }, [userId]); // 正确追踪变化
}
更优方案: 转向事件驱动的请求方式或使用数据库。
jsx
// ✅ 更优写法:无effect,仅使用loader
function UserProfile({ userId }) {
  const user = use(userId); // React 19的use API
  // 或使用React Query、SWR等库
}

Decision Tree: useEffect vs Alternatives

决策树:useEffect vs 替代方案

Need to sync with external system?
├─ Yes (browser APIs, websockets, timers)
│  └─ Use useEffect
└─ No (pure React application logic)
   ├─ Derived state calculation?
   │  ├─ Yes → Calculate during render
   │  └─ No → Continue...
   ├─ User action triggered?
   │  ├─ Yes → Use event handler
   │  └─ No → Continue...
   ├─ State reset needed?
   │  ├─ Yes → Use key prop
   │  └─ No → Continue...
   └─ Really need effect after re-think?
      └─ Yes → Use useState/useReducer/setState pattern

是否需要与外部系统同步?
├─ 是(浏览器API、websocket、定时器)
│  └─ 使用useEffect
└─ 否(纯React应用逻辑)
   ├─ 是否为派生状态计算?
   │  ├─ 是 → 在渲染阶段计算
   │  └─ 否 → 继续...
   ├─ 是否由用户操作触发?
   │  ├─ 是 → 使用事件处理器
   │  └─ 否 → 继续...
   ├─ 是否需要重置状态?
   │  ├─ 是 → 使用key prop
   │  └─ 否 → 继续...
   └─ 重新思考后是否真的需要effect?
      └─ 是 → 使用useState/useReducer/setState模式

Patterns to Always Avoid

需始终避免的模式

❌ 1. useEffect for DOM Manipulation (without refs)

❌ 1. 无refs的情况下使用useEffect操作DOM

jsx
// BAD
function Modal() {
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);
}
✅ Better: Use refs or layout effects when needed.

jsx
// 不良写法
function Modal() {
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);
}
✅ 更优方案: 必要时使用refs或layout effects。

❌ 2. useEffect for Logging

❌ 2. 使用useEffect进行日志记录

jsx
// BAD
function App({ data }) {
  useEffect(() => {
    console.log('Data changed:', data);
  }, [data]);
}
✅ Better: Log in event handler or use
React DevTools
profiling.

jsx
// 不良写法
function App({ data }) {
  useEffect(() => {
    console.log('Data changed:', data);
  }, [data]);
}
✅ 更优方案: 在事件处理器中记录日志,或使用
React DevTools
分析。

❌ 3. useEffect for Component Initialization

❌ 3. 使用useEffect进行组件初始化

jsx
// BAD
function Widget() {
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    someLibrary.init();
    setInitialized(true);
  }, []);
}
✅ Better: Initialize outside component effect:
jsx
// GOOD
let initialized = false;

function Widget() {
  if (!initialized) {
    someLibrary.init();
    initialized = true;
  }
}
Or use library-specific initialization patterns.

jsx
// 不良写法
function Widget() {
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    someLibrary.init();
    setInitialized(true);
  }, []);
}
✅ 更优方案: 在组件effect外部初始化:
jsx
// 推荐写法
let initialized = false;

function Widget() {
  if (!initialized) {
    someLibrary.init();
    initialized = true;
  }
}
或使用库特定的初始化模式。

Quick Reference

快速参考

❌ Don't use useEffect for:

❌ 不应使用useEffect的场景:

ScenarioProblemAlternative
Derived stateDouble renderCalculate during render
State resetsStale dataUse
key
prop
User actionsLost intentEvent handlers
List filteringExtra rendersFilter in render
Browser APIsTearing bugs (concurrent)
useSyncExternalStore
Form submissionFragile flag patternDirect async handler
Data fetchingManual cache managementReact Query, SWR, Suspense
场景问题替代方案
派生状态双重渲染在渲染阶段计算
状态重置数据过期使用
key
prop
用户操作丢失意图事件处理器
列表过滤额外渲染在渲染阶段过滤
浏览器API并发撕裂bug
useSyncExternalStore
表单提交脆弱的标记位模式直接异步处理器
数据请求手动缓存管理React Query、SWR、Suspense

✅ DO use useEffect for:

✅ 应使用useEffect的场景:

  • Subscribing to external systems (websockets, browser APIs, etc.)
  • Setting up timers with cleanup
  • Managing third-party library integration
  • Document title changes
  • Analytics/telemetry when rendering completes

  • 订阅外部系统(websocket、浏览器API等)
  • 设置带清理操作的定时器
  • 管理第三方库集成
  • 修改文档标题
  • 渲染完成后的分析/遥测

React 19: New Alternatives

React 19:新替代方案

React 19 introduces the
use
API for reading resources in render:
jsx
// React 19+ - Direct resource reading
function UserProfile({ userId }) {
  const user = use(fetchUser(userId)); // Reads promise directly

  return <div>{user.name}</div>;
}
This eliminates many data-fetching useEffect patterns entirely.

React 19引入了
use
API,用于在渲染阶段读取资源:
jsx
// React 19+ - 直接读取资源
function UserProfile({ userId }) {
  const user = use(fetchUser(userId)); // 直接读取Promise

  return <div>{user.name}</div>;
}
这彻底消除了许多基于effect的数据请求模式。

References and Further Reading

参考资料与扩展阅读

Official Documentation

官方文档

Articles from Senior Engineers

资深工程师文章

Key Principles

核心原则

  1. Effects are escape hatches - use only when stepping outside React
  2. Event-driven > Effect-driven - prefer handlers for user actions
  3. Render-time > Effect-time - calculate values during render
  4. Single source of truth - avoid duplicating state in effects
  5. Concurrent-safe - use specialized hooks for external subscriptions
  1. Effects是逃生舱 - 仅在脱离React环境时使用
  2. 事件驱动 > Effect驱动 - 优先为用户操作使用处理器
  3. 渲染阶段 > Effect阶段 - 在渲染阶段计算值
  4. 单一数据源 - 避免在effects中复制状态
  5. 并发安全 - 对外部订阅使用专用hooks