设置状态时重新渲染过多 - useSWR

use*_*989 6 reactjs next.js swr

我正在使用useSWR来获取数据,然后使用这些数据,我想通过使用reduce 来获得总计。如果我 console.log 输出该值,它工作正常,但一旦我尝试使用该值设置状态,我就会收到“太可能重新渲染”消息。

import Admin from "../../../components/admin/Admin";
import { useRouter } from "next/router";
import styles from "../../../styles/Dashboard.module.css";
import { getSession, useSession } from "next-auth/client";
import { useState } from "react";

/* BOOTSTRAP */
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Spinner from "react-bootstrap/Spinner";
import Table from "react-bootstrap/Table";

import useSWR from "swr";
import axios from "axios";

const General = () => {
  const [session, loading] = useSession();
  const [adults, setAdults] = useState(null);
  const router = useRouter();
  const { id } = router.query;

  const fetcher = (url) =>
    axios
      .get(url, {
        headers: { Authorization: "Bearer " + session.user.servertoken },
      })
      .then((res) => res.data);

  const { data, error } = useSWR(
    `http://localhost:8000/api/admin/general/${id}`,
    fetcher
  );

   if (data) {
     const adults = data.map((a) => a.adults);
     const reducer = (accumlator, item) => {
       return accumlator + item;
     };
     const totalAdults = adults.reduce(reducer, 0);
     setAdults(totalAdults);
   }

  return (
    <Admin>
      <div className={styles.admin_banner}>
        <Container fluid>
          <Row>
            <Col>
              <h2>Bookings</h2>
              <h6>
                {adults}
              </h6>
            </Col>
          </Row>
        </Container>
      </div>
      <Container fluid>
        <Row>
          <Col>
            <div className={styles.admin_container}>
              {!error && !data && (
                <Spinner animation="border" role="status">
                  <span className="sr-only">Loading...</span>
                </Spinner>
              )}
              {!error && data && (
                <Table responsive="md">
                  <thead>
                    <tr>
                      <th>Name</th>
                    </tr>
                  </thead>
                  <tbody>
                    {data &&
                      !error &&
                      data.map((d) => (
                        <tr key={d._id}>
                          <td>
                            {d.firstName} {d.lastName}
                          </td>
                        </tr>
                      ))}
                  </tbody>
                </Table>
              )}
            </div>
          </Col>
        </Row>
      </Container>
    </Admin>
  );
};

export async function getServerSideProps(context) {
  const session = await getSession({
    req: context.req,
  });

  if (!session) {
    return {
      redirect: {
        destination: "/admin",
        permanent: false,
      },
    };
  } else {
    return { props: session };
  }
}

export default General;
Run Code Online (Sandbox Code Playgroud)

men*_*t_a 10

您应该使用依赖于数据值的 useEffect,以便仅当数据在渲染之间发生更改时才可以更新它,接受的答案使用了 useEffect 内的 useSWR 挂钩,这是不正确的

例子:

const General = () => {
  const [session, loading] = useSession();
  const [adults, setAdults] = useState(null);
  const router = useRouter();
  const { id } = router.query;

  const fetcher = (url) =>
    axios
      .get(url, {
        headers: { Authorization: "Bearer " + session.user.servertoken },
      })
      .then((res) => res.data);

  const { data, error } = useSWR(
    `http://localhost:8000/api/admin/general/${id}`,
    fetcher
  );

  useEffect(() => {
   const adultsFetch = data.map((a) => a.adults);
   const reducer = (accumlator, item) => {
     return accumlator + item;
   };
   const totalAdults = adultsFetch.reduce(reducer, 0);
   setAdults(totalAdults);
  }, [data]);

}
Run Code Online (Sandbox Code Playgroud)

编辑:

您还可以使用 useMemo 挂钩,这样您甚至不再需要 useState,useMemo 会在每次数据依赖关系发生变化时重新计算成年人。

欲了解更多信息:https://reactjs.org/docs/hooks-reference.html#usememo

const General = () => {
  const [session, loading] = useSession();
  const router = useRouter();
  const { id } = router.query;

  const fetcher = (url) =>
    axios
      .get(url, {
        headers: { Authorization: "Bearer " + session.user.servertoken },
      })
      .then((res) => res.data);

  const { data, error } = useSWR(
    `http://localhost:8000/api/admin/general/${id}`,
    fetcher
  );

  const adults = useMemo(() => {
    if (!data) return null;
    const adultsFetch = data.map((a) => a.adults);
    const reducer = (accumlator, item) => {
      return accumlator + item;
    };
    const totalAdults = adultsFetch.reduce(reducer, 0);
    return totalAdults;
  }, [data]);

}
Run Code Online (Sandbox Code Playgroud)


小智 10

您可以通过添加第三个参数(选项)来实现此目的,如下所示:

const { data, error } = useSWR(
    `http://localhost:8000/api/admin/general/${id}`,
    fetcher,
    {
        revalidateOnFocus: false,
        revalidateIfStale: false,
        // revalidateOnReconnect: false, // personally, I didn't need this one
    }

  );
Run Code Online (Sandbox Code Playgroud)

它基本上缓存数据并防止一旦有缓存就重新验证。这是文档的链接。

或者,根据文档,useSWRImmutable从版本 1.0 开始,您可以使用 来获得与默认设置这些选项相同的结果。

import useSWRImmutable from "swr/immutable"

const { data, error } = useSWRImmutable(`http://localhost:8000/api/admin/general/${id}`, fetcher)
Run Code Online (Sandbox Code Playgroud)


Gh0*_*05d -1

问题是您的setAdults(totalAdults);数据检查导致了无限循环。React中的 Hook会导致组件重新渲染,所以实际发生的情况是

data为 true -->setAdults被触发 --> 重新渲染 -->data为 true --> ...

将您的逻辑移至useEffect挂钩中:

React.useEffect(() => {
  const fetcher = url =>
    axios
      .get(url, {
        headers: { Authorization: "Bearer " + session.user.servertoken },
      })
      .then(res => {
        const adults = data.map(a => a.adults);
        const reducer = (accumlator, item) => {
          return accumlator + item;
        };
        const totalAdults = adults.reduce(reducer, 0);
        setAdults(totalAdults);
      });

  useSWR(`http://localhost:8000/api/admin/general/${id}`, fetcher);
}, []);
Run Code Online (Sandbox Code Playgroud)

还要更新您的加载和错误检查。也许为两者创建一个新的状态变量。

  • 我收到违反规则消息:React Hook“useSWR”无法在回调内调用。React Hooks 必须在 React 函数组件或自定义 React Hook 函数中调用。(react-hooks/rules-of-hooks)eslint 在沙箱中存在任何替代方案,为什么使用一个会产生其他问题只是愚蠢的时间浪费不是吗?我知道这听起来太自大了,浪费了很多时间 (3认同)
  • 这不是一个正确的解决方案,正如之前所说,useSWR 不能在 useEffect 挂钩内调用 + 将加载和错误状态置于新状态只是浪费 useSWR 的潜力,它为您提供一切以避免不必要的状态和代码。 (2认同)