【问题标题】:"React has detected a change in the order of Hooks" but Hooks seem to be invoked in order“React 检测到 Hooks 的顺序发生了变化”,但 Hooks 似乎是按顺序调用的
【发布时间】:2019-12-15 06:25:22
【问题描述】:

我正在尝试通过 React 的钩子使用 ContextReducers,但遇到了钩子顺序不一致的问题。我的理解是,只要useHook(…) 的顺序保持不变,就可以在任何类型的控制流中调用返回的状态/更新函数/reducer。否则,我将在 FunctionComponents 的开头调用钩子。

是我在循环中生成Days 吗?还是遗漏了什么?

Warning: React has detected a change in the order of Hooks
called by Container. This will lead to bugs and errors if not fixed. For
more information, read the Rules of Hooks:
https://reactjs.org/docs/hooks-rules.html

   Previous render            Next render
   ------------------------------------------------------
1. useContext                 useContext
2. undefined                  useRef
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Container的完整版如下。下面是Day 的摘录,并有来自react-dnduseDrop 的参考。

export const Container: FunctionComponent<Props> = () => {
  let events = useContext(State.StateContext)
  //let events: Array<Event.Event> = [] <- with this, no warning

  const getDaysEvents = (day: Event.Time, events: Array<Event.Event>) => {
    return events.map(e => {
      const isTodays = e.startTime.hasSame(day, "day")
      return isTodays && Event.Event({ dayHeight, event: e })
    })
  }

  let days = []
  for (let i = 0; i < 7; i++) {
    const day = DateTime.today().plus({ days: i })
    days.push(
      <Day key={day.toISO()} height={dayHeight} date={day}>
        {getDaysEvents(day, events)}
      </Day>
    )
  }
  return <div className="Container">{days}</div>
}

摘自DayEvent 同样使用useDrag 钩子,也像这里一样在顶层调用)。

const Day: FunctionComponent<DayProps> = ({ date, height, children }) => {
  const dispatch = useContext(State.DispatchContext)
  const [{ isOver, offset }, dropRef] = useDrop({
    // …uses the dispatch function within…
    // …
  })
  // …
}

【问题讨论】:

  • 这里没有看到 useRef ...
  • 它在Day 组件内,而不是在Container 内。
  • 该消息表明挂钩是由Container 调用的,而不是由“Day”调用的。这似乎很奇怪。 Event.Event() 有没有使用钩子?
  • 是的,它是useDrag,在顶层。使用来自它的 ref,以及闭包中的监视器返回的值。很高兴包含它,不过它与 DayContainer 是一样的。
  • 如果 Event.Event 是像 Day 这样的反应组件(我从您的评论中了解到),问题是您直接调用它 Event.Event() 而不是返回 jsx return isTodays &amp;&amp; &lt;Event.Event dayHeight={dayHeight} event={e} /&gt;

标签: javascript reactjs typescript


【解决方案1】:

由于使用短路逻辑,我在编写的组件中遇到了同样的错误消息。

这导致了一个错误:

const x = useSelector(state => state.foo);
if (!x) { return ; }
const y = useSelector(state => state.bar);

这是因为当x 为真时,钩子列表的长度为2,但当x 为假时,列表的长度为1。

为了解决这个错误,我必须在提前终止之前使用所有钩子。

const x = useSelector(state => state.foo);
const y = useSelector(state => state.bar);
if (!x) { return ; }

【讨论】:

  • 这里也一样,我做了 const x = something ? 0 : useMemo();
  • 谢谢!同样,不可能有条件地使用选择器。例如:let client = client_id &amp;&amp; useSelector(state =&gt; state.clients[client_id]) 不好,但 let client = useSelector(state =&gt; client_id ? state.clients[client_id] : null) 工作正常。
【解决方案2】:

写下我的评论作为答案:

问题是你直接调用Event.Event(),即使它是一个反应组件。这会导致 react 将函数内部的钩子调用视为 Container 的一部分,即使您打算让它们成为 Event 的一部分。

解决办法是使用JSX:

return isTodays &amp;&amp; &lt;Event.Event dayHeight={dayHeight} event={e} /&gt;

当你用生成的 JS 代码替换 JSX 时,为什么会更清楚:

return isTodays &amp;&amp; React.createElement(Event.Event, { dayHeight, event: e })

https://reactjs.org/docs/react-api.html#createelement。您永远不想直接调用函数组件,react 的工作原理是您始终将引用组件传递给 react 并让它在正确的时间调用函数。

【讨论】:

  • 它发生在我身上,当一个被破坏的道具对象在组件中未定义时,我通过删除未使用的被破坏的道具对象来解决这个问题。
【解决方案3】:

当你收到这个error时,不是这个问题,而是其他原因

这实际上是由于钩子实现的任何不良做法而发生的

1 - 仅在顶层调用 Hooks

不要在循环条件嵌套函数中调用 Hooks。相反,请始终在 React 函数的顶层使用 Hooks

注意:首先在函数顶部实现 useState 钩子

2 - 仅来自 React 函数的调用挂钩

不要从常规 JavaScript 函数调用 Hooks

3 - 测试期间出错

如果您在测试组件时遇到此错误,请注意设置自定义挂钩的位置(替换到函数顶部)

最佳实践

使用 eslint 对代码进行 lint 以避免出现 React Hooks Rules 错误

使用 npm 或 yarn 安装包

npm install eslint-plugin-react-hooks --save-dev

【讨论】:

    【解决方案4】:

    这不是问题场景,而是错误本身,希望它能帮助某人:)

      const { chatSession, userBelongsToSession } = useChatSession(session_id)
      const { activeSession, setActiveSession } = useActiveChatSession()
    
      const isCurrentActiveSession = useMemo(() => activeSession != null && activeSession.session_id === session_id, [activeSession, session_id])
    
      if (chatSession == null || activeSession == null) {
        return (<div></div>)
      }
    
      const Container = styled(BorderedContainer)`height: 72px; cursor: pointer;`
    

    我在这段代码中也发生了同样的错误,这与 useRef 被 styled-components 调用而之前由于条件渲染而没有被调用有关

    if (chatSession == null || activeSession == null) {
      return (<div></div>)
    }
    

    默认情况下,hook 将返回 null,并且我的组件将在没有 useRef 的情况下呈现,尽管当实际填充 hooks 时,styled-components 将生成一个带有 useRef 的组件。

      if (chatSession == null || activeSession == null) {
        return (<div></div>)
      }
    
    const Container = styled(BorderedContainer)`height: 72px; cursor: pointer;
    

    【讨论】:

      【解决方案5】:

      我在编写的测试中调用不同的钩子方法时随机收到此错误。 对我的修复是在我实施的useRef 的间谍中:

      const useRefSpy = jest
        .spyOn(React, 'useRef')
        .mockReturnValueOnce({ whatever })
      

      mockReturnValueOnce 更改为mockReturnValue 修复了错误。

      【讨论】:

        【解决方案6】:

        从 useEffect 调用多个 api 调用

        在我的情况下,我正在执行多个 api 调用并将它们中的每一个保存到不同的状态,这导致了我这个错误。但在将其更改为单一状态后,我能够克服这一点。

        const [resp, setGitData] = useState({ data: null, repos: null });
        
          useEffect(() => {
            const fetchData = async () => {
              const respGlobal = await axios(
                `https://api.github.com/users/${username}`
              );
              const respRepos = await axios(
                `https://api.github.com/users/${username}/repos`
              );
        
              setGitData({ data: respGlobal.data, repos: respGlobal.data });
            };
        
            fetchData();
          }, []);
        

        【讨论】:

          【解决方案7】:

          我在 react-native 应用程序中也遇到了同样的问题。这是由于不必要的元素加载过程而发生的。

          旧代码

          return (......
               <NotificationDialog
                    show={notificationPayload !== null}
                    title={notificationPayload?.title}
                    .......
                  />
          );
          

          这将重新渲染 NotificationDialog 甚至 notificationPayload !== null。但这不是必需的。只是我添加了这个空检查并避免在它为空时呈现这个 NotificationDialog

          修复

            return (......
                   { notificationPayload !== null? <NotificationDialog
                        show={notificationPayload !== null}
                        title={notificationPayload?.title}
                        .......
                      /> : null } 
              );
          

          【讨论】:

            猜你喜欢
            • 2021-10-02
            • 2021-11-05
            • 2021-12-15
            • 1970-01-01
            • 2022-10-02
            • 2020-07-04
            • 1970-01-01
            • 2022-01-21
            • 1970-01-01
            相关资源
            最近更新 更多