reactjs “React已检测到挂钩顺序的更改”,但挂钩似乎按顺序调用

zyfwsgd6  于 2022-11-22  发布在  React
关注(0)|答案(9)|浏览(133)

我试图通过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…
    // …
  })
  // …
}
oaxa6hgo

oaxa6hgo1#

由于使用了短路逻辑,我在编写组件时遇到了同样的错误消息。
这会导致错误:

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 ; }
dgenwo3n

dgenwo3n2#

写我的评论作为回答:
问题是你直接调用了Event.Event(),尽管它是一个react组件,这会导致react将函数内部的钩子调用视为Container的一部分,尽管你本打算将它们作为Event的一部分。
解决方案是使用JSX:
return isTodays && <Event.Event dayHeight={dayHeight} event={e} />
当您将JSX替换为生成的JS代码时,就可以更清楚地了解为什么会这样了:
return isTodays && React.createElement(Event.Event, { dayHeight, event: e })
请参见https://reactjs.org/docs/react-api.html#createelement。您永远不想直接调用函数组件,react的工作原理是,您总是将一个引用传递给要做出React的组件,让它在正确的时间调用函数。

tpgth1q7

tpgth1q73#

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

它实际上是由于任何不好的钩子实现实践而发生的

1 -仅顶级的呼叫挂接

不要在循环条件嵌套函数内调用挂钩。相反,应始终在React函数的顶层使用挂钩
注意:首先在函数顶部实现useState挂钩

2 -仅从React函数调用挂接

不要从常规JavaScript函数调用挂钩

3 -测试期间出现错误

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

最佳实践

对lint使用eslint,您的代码将避免出现React Hooks规则错误
安装带有npm或Yarn的卷装

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

7uzetpgm4#

从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();
  }, []);
rjee0c15

rjee0c155#

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

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有关,而之前由于条件渲染而没有调用useRef

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

默认情况下,钩子将返回null,我的组件将不使用useRef呈现,尽管当钩子实际被填充时,样式化组件将生成一个使用useRef的组件。

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

const Container = styled(BorderedContainer)`height: 72px; cursor: pointer;
fafcakar

fafcakar6#

在我编写的测试中,当我调用不同的钩子方法时,我随机地得到了这个错误。我的修复方法是在我实现的useRef的spy中:

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

mockReturnValueOnce更改为mockReturnValue修复了错误。

g6baxovj

g6baxovj7#

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

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

这将重新呈现NotificationDialog,即使notificationPayload!== null。但这不是必需的。我只是添加了此null检查,并避免在此NotificationDialog为null时呈现它
固定了

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

fae0ux8s8#

我通过将导入的组件作为组件而不是函数来修复我的问题,我必须返回jsx(<>{values}</>)而不仅仅是值,因为我必须在ConvertDate中导入redux的useSelector,显然如果它只是一个函数,它就不能与useSelector一起工作。我可以只使用一个函数,并将日期和redux的状态作为属性来传递...
我拥有的:

<b>{ConvertDate({ date: post.dateTime })}</b>

还有我是怎么解决的:

<b>{<ConvertDate date={post.dateTime} />}</b>
cl25kdpy

cl25kdpy9#

我知道我迟到了,但我只是想分享我的经验,在同一个错误堆栈。
对于我来说,这种情况发生在tanstack的useQuery 4和ReactJS 17中的useEffect的组合上。
一个美丽的讨论here
堆栈跟踪:

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

   Previous render            Next render
   ------------------------------------------------------
1. useContext                 useContext
2. useContext                 useContext
3. useState                   useState
4. useRef                     useRef
5. useEffect                  useEffect
6. useRef                     useRef
7. useEffect                  useEffect
8. useContext                 useContext
9. useState                   useState
10. useRef                    useRef
11. useEffect                 useEffect
12. useRef                    useRef
13. useEffect                 useEffect
14. useState                  useState
15. useState                  useState
16. useState                  useState
17. useState                  useState
18. useState                  useState
19. useState                  useState
20. useContext                useContext
21. useContext                useContext
22. useContext                useContext
23. useContext                useContext
24. useEffect                 useEffect
25. useState                  useState
26. useCallback               useCallback
27. useState                  useState
28. useLayoutEffect           useLayoutEffect
29. useEffect                 useEffect
30. useDebugValue             useDebugValue
31. useEffect                 useEffect
32. undefined                 useEffect
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

因此,它发生了,因为我在useQuery API回调行之后使用了useEffect,我通过将useEffect放在useQuery行之前解决了这个问题,所有警告堆栈跟踪都得到了解决。
希望能帮到什么人。

相关问题