【问题标题】:React has detected a change in the order of Hooks errorReact 检测到 Hooks 错误的顺序发生了变化
【发布时间】:2021-12-15 01:34:30
【问题描述】:

我尝试使用 useSWRInfinite 钩子实现无限滚动。

从 Youtube 教程中找到代码 ????并且几乎没有改动。

但得到一个名为 “React 检测到 InfiniteDataList 调用的 Hooks 的顺序发生变化。”的错误。

2 小时的调试 -- 没有找到解决方案。

Picture of the error

Youtube 教程代码 --> https://github.com/gdangelo/micro-blogging-workshop/blob/main/components/InfiniteDataList.js Youtube 教程链接 --> https://www.youtube.com/watch?v=FsngdxyvFrQ

我的密码:

InfiniteDataList.js

import React, { useEffect, useRef } from "react";
import { useInfiniteQuery } from "../../hooks";
import MessageWrapper from "../../UI/MessageWrapper";
import { isInViewport } from "../../Utility/windowUtils";
import { useDebouncedCallback } from "use-debounce";
import DefaultListItemComponent from "./components/DefaultListItemComponent";
import DefaultContainerComponent from "./components/DefaultContainerComponent";
import DefaultLoadMoreComponent from "./components/DefaultLoadMoreComponent";

const InfiniteDataList = ({
  queryKey,
  initialData = [],
  listItemComponent: ListItemComponent = DefaultListItemComponent,
  containerComponent: ContainerComponent = DefaultContainerComponent,
  onError = () => {},
  onEmpty = () => {},
  onEmptyComponent: OnEmptyComponent = null,
  onNoMoreData = () => {},
  noMoreDataComponent: NoMoreDataComponent = null,
  isAutoLoadMoreAtEnd = true,
  autoLoadMoreAtEndOptions: {
    timeout = 500,
    onLoadMoreDetected = () => {},
  } = {},
  loadMoreComponent: LoadMoreComponent = DefaultLoadMoreComponent,
}) => {
  // hooks
  const {
    data,
    error,
    hasNextPage,
    fetchNextPage,
    isFetchingInitialData,
    isFetchingNextPageData,
  } = useInfiniteQuery(queryKey, { initialData });
  const moreRef = useRef();

  const loadMore = useDebouncedCallback(() => {
    if (isInViewport(moreRef.current)) {
      onLoadMoreDetected();
      fetchNextPage();
    }
  }, timeout);
  const getLoadMoreRef = () => moreRef;

  useEffect(() => {
    if (isAutoLoadMoreAtEnd) {
      window.addEventListener("scroll", loadMore);
    }
    return () => window.removeEventListener("scroll", loadMore);
  }, []);

  // some configuration
  OnEmptyComponent = OnEmptyComponent && (() => <h4>No Details found</h4>);
  NoMoreDataComponent =
    NoMoreDataComponent &&
    (() => <MessageWrapper message="No More Data found !" />);

  // helper utils
  const infiniteQueryProps = {
    data,
    error,
    hasNextPage,
    fetchNextPage,
    isFetchingInitialData,
    isFetchingNextPageData,
  };

  // if error occurs
  if (error) {
    onError(error);
    console.log("error");
  }

  // no data found
  if (!isFetchingInitialData && data?.length === 0) {
    onEmpty();
    console.log(typeof OnEmptyComponent);
    return <OnEmptyComponent />;
  }

  // no more data to load
  if (!hasNextPage) {
    onNoMoreData();
  }

  return (
    <ContainerComponent loading={isFetchingInitialData}>
      {data?.map((item, index) => (
        <ListItemComponent key={index} {...item} />
      ))}
      {hasNextPage ? (
        <LoadMoreComponent
          {...infiniteQueryProps}
          getLoadMoreRef={getLoadMoreRef}
        />
      ) : (
        <NoMoreDataComponent {...infiniteQueryProps} />
      )}
    </ContainerComponent>
  );
};

export default InfiniteDataList;


useInfiniteQuery.js


import useSWRInfinite from "swr/infinite";
import { axiosInstance } from "../Utility/axiosInstance";

function getFetcher(requestType = "get") {
  return (url, dataToPost) =>
    axiosInstance[requestType](url, dataToPost).then((res) => res.data);
}

export function useInfiniteQuery(
  queryKey,
  { initialData, requestType = "get" }
) {
  const { data, error, size, setSize } = useSWRInfinite(
    (pageIndex, previousPageData) => {
      // reached the end
      if (previousPageData && !previousPageData.after) return null;
      // first page
      if (pageIndex === 0) return queryKey;
      // next pages
      const search = queryKey.includes("?");
      return `${queryKey}${search ? "$" : "?"}cursor=${encodeURIComponent(
        JSON.stringify(previousPageData.after)
      )}`;
    },
    getFetcher(requestType),
    initialData
  );

  // to fetch next page from react component
  function fetchNextPage() {
    setSize((prev) => prev + 1);
  }

  // flatten all the data obtained so far to a single array
  const flattenPages = data?.flatMap((page) => page.data) ?? [];

  // indicates whether the api will have data for another page
  const hasNextPage = !!data?.[size - 1]?.after;

  // isLoading for initial request
  const isFetchingInitialData = !data && !error;

  // isLoading for other requests including the initial request
  const isFetchingNextPageData =
    isFetchingInitialData ||
    (size > 0 && data && typeof data[size - 1] === "undefined");

  return {
    data: flattenPages,
    error,
    hasNextPage,
    fetchNextPage,
    isFetchingInitialData,
    isFetchingNextPageData,
  };
}


isInViewport.js

// Check if element is visible inside the viewport
export function isInViewport(element) {
  if (!element) return false;

  const rect = element.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <=
      (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

DefaultLoadMoreComponent.js

import React, { useState } from "react";
const DefaultLoadMoreComponent = ({ getLoadMoreRef = () => {} }) => {
  const ref = getLoadMoreRef();
  return <div ref={ref} />;
};
export default DefaultLoadMoreComponent;


DefaultListItemComponent.js


import React from "react";
const DefaultListItemComponent = ({ children = [] }) => <div>{children}</div>;

export default DefaultListItemComponent;


DefaultContainerComponent.js

import React from "react";
import AsyncDiv from "../../../UI/AsyncDiv";

const DefaultContainerComponent = ({ children = [], ...rest }) => (
  <AsyncDiv {...rest}>{children}</AsyncDiv>
);

export default DefaultContainerComponent;


我渲染 InfiniteDataList 组件的组件

import React from "react";
import InfiniteDataList from "../../../../../UI/InfiniteDataList";
import PaginatedLeads from "./components/PaginatedLeads";
import { getError } from "../../../../../Utility/apiUtils";

const ViewAllLeads = (props) => {
  return (
    <InfiniteDataList
      initialData={[]}
      listItemComponent={PaginatedLeads}
      onError={(err) =>
        window.flash({ title: getError(err).message, type: "error" })
      }
      queryKey="/employee/leads"
    />
  );
};

export default ViewAllLeads;


PaginatedLeads.js


import React from "react";

const PaginatedLeads = (props) => {
  console.log(props);
  return <div>PaginatedLeads</div>;
};

export default PaginatedLeads;

【问题讨论】:

  • 此错误通常意味着您以有条件的方式使用hooks,因此声明它们的顺序会根据条件而变化。你不能以这种方式使用钩子,因为 React 无法检测到钩子的先前值。我没有在你的代码中发现像这样的潜在问题,但你可以看看你代码库的其他部分,看看你是否不小心有条件地使用了一些钩子。
  • @AntonioPantano 感谢帮助。最后我发现了问题。它实际上是一个语法错误。我会回答这个问题。

标签: reactjs react-hooks vercel rerender use-ref


【解决方案1】:

这是我的错。

useInfiniteQuery.js 文件中,我以错误的格式传递了初始数据。

语法错误——查看最后一行(initialData)

    const { data, error, size, setSize } = useSWRInfinite(
        (pageIndex, previousPageData) => {
          // reached the end
          if (previousPageData && !previousPageData.after) return null;
          // first page
          if (pageIndex === 0) return queryKey;
          // next pages
          const search = queryKey.includes("?");
          return `${queryKey}${search ? "&" : "?"}cursor=${encodeURIComponent(
            JSON.stringify(previousPageData.after)
          )}`;
        },
        getFetcher(requestType),
        initialData
      );

正确的语法——见最后一行({ fallbackData: initialData })

      const { data, error, size, setSize } = useSWRInfinite(
        (pageIndex, previousPageData) => {
          // reached the end
          if (previousPageData && !previousPageData.after) return null;
          // first page
          if (pageIndex === 0) return queryKey;
          // next pages
          const search = queryKey.includes("?");
          return `${queryKey}${search ? "&" : "?"}cursor=${encodeURIComponent(
            JSON.stringify(previousPageData.after)
          )}`;
        },
        getFetcher(requestType),
        { fallbackData: initialData }
      );

【讨论】:

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