【问题标题】:Custom react hook triggers api call multiple times自定义反应钩子多次触发api调用
【发布时间】:2020-01-12 19:36:44
【问题描述】:

我不知道如何处理重复调用我的 api 的函数组件。我有两个检索数​​据的组件,其中一个调用 api 两次。一次在第二个组件之前一次。

我正在使用自定义反应钩子和 axios get 方法来检索数据。我的两个组件是嵌套的。加载和获取数据时的第一个组件。在这个组件内部是一个子组件,当渲染它时,它会在将第一组数据作为道具传递给另一个子组件之前获取数据。当它完成加载时,它会重新加载第一个子组件,该子组件再次调用 api 获取数据。我了解功能组件在状态更改时重新加载。我很高兴它不会再次调用 api。有没有办法检查它是否已经有数据并绕过 api 调用?

用于检索数据的自定义挂钩

import React, { useState, useEffect, useReducer } from "react";
import axios from "axios";

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_INIT":
      return { ...state, isLoading: true, hasErrored: false };
    case "FETCH_SUCCESS":
      return {
        ...state,
        isLoading: false,
        hasErrored: false,
        errorMessage: "",
        data: action.payload
      };
    case "FETCH_FAILURE":
      return {
        ...state,
        isLoading: false,
        hasErrored: true,
        errorMessage: "Data Retrieve Failure"
      };
    case "REPLACE_DATA":
      // The record passed (state.data) must have the attribute "id"
      const newData = state.data.map(rec => {
        return rec.id === action.replacerecord.id ? action.replacerecord : rec;
      });
      return {
        ...state,
        isLoading: false,
        hasErrored: false,
        errorMessage: "",
        data: newData
      };
    default:
      throw new Error();
  }
};

const useAxiosFetch = (initialUrl, initialData) => {
  const [url] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    hasErrored: false,
    errorMessage: "",
    data: initialData
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: "FETCH_INIT" });

      try {
        let result = await axios.get(url);
        if (!didCancel) {
          dispatch({ type: "FETCH_SUCCESS", payload: result.data });
        }
      } catch (err) {
        if (!didCancel) {
          dispatch({ type: "FETCH_FAILURE" });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  const updateDataRecord = record => {
    dispatch({
      type: "REPLACE_DATA",
      replacerecord: record
    });
  };

  return { ...state, updateDataRecord };
};

export default useAxiosFetch;

在内部两次呈现“CompaniesDropdown”的主组件

CompaniesDropdown 是 ListFilterContainer 组件中的三个下拉菜单之一,但也是唯一一个多次调用 api 的下拉菜单。其他两个下拉列表通过选择 CompaniesDropdown 加载。

import React, { useMemo, useEffect, useContext } from "react";
import InvoiceList from "../src/Components/Lists/InvoiceList";
import useAxiosFetch from "../src/useAxiosFetch";
import { ConfigContext } from "./_app";
import ListFilterContainer from "../src/Components/Filters/InvoiceFilters";
// import "../css/ListView.css";

const Invoices = props => {
  const context = useContext(ConfigContext);

  useEffect(() => {
    document.title = "Captive Billing :: Invoices";
  });

  const {
    data,
    isLoading,
    hasErrored,
    errorMessage,
    updateDataRecord
  } = useAxiosFetch("https://localhost:44394/Invoice/GetInvoices/false", []);

  const newInvoicesList = useMemo(
    () => data
    //     .filter(
    //       ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
    //     )
    //     .sort(function(a, b) {
    //       if (a.firstName < b.firstName) {
    //         return -1;
    //       }
    //       if (a.firstName > b.firstName) {
    //         return 1;
    //       }
    //       return 0;
    //     }),
    // [speakingSaturday, speakingSunday, data]
  );

  const invoices = isLoading ? [] : newInvoicesList;

  if (hasErrored)
    return (
      <div>
        {errorMessage}&nbsp;"Make sure you have launched "npm run json-server"
      </div>
    );

  if (isLoading) return <div>Loading...</div>;

  const dataProps = {
    data: invoices,
    titlefield: "invoiceNumber",
    titleHeader: "Invoice Number:",
    childPathRoot: "invoiceDetail",
    childIdField: "invoiceId",
    childDataCollection: "invoiceData"
  };

  var divStyle = {
    height: context.windowHeight - 100 + "px"
  };

  return (
    <main>
      <ListFilterContainer />
      <section style={divStyle} id="invoices" className="card-container">
        <InvoiceList data={dataProps} />
      </section>
    </main>
  );
};

Invoices.getInitialProps = async ({ req }) => {
  const isServer = !!req;
  return { isServer };
};

export default Invoices;

实际结果如上所述。我主要关心的是不能多次调用 api。

这里有一些额外的代码可以提供帮助。就是上面提到的过滤控制。正如您会注意到的那样,它实际上只包含下拉列表和一个文本框。第一个下拉列表是调用 api 两次的下拉列表。后两个在被选中之前是不可见的。

import React, { useState, useMemo } from "react";
import CompaniesDropdown from "../Dropdowns/CompaniesDropdown";
import LocationsDropdown from "../Dropdowns/LocationsDropdown";
import AccountsDropdown from "../Dropdowns/AccountsDropdown";
import Search from "./SearchFilter/SearchFilter";

const InvoiceFilters = props => {
  const [company, setCompany] = useState("");
  const [location, setLocation] = useState(undefined);
  const [account, setAccount] = useState(undefined);

  const handleClientChange = clientValue => {
    setCompany(clientValue);
  };

  const handleLocationsChange = locationValue => {
    setLocation(locationValue);
  };

  const handleAccountsChange = AccountValue => {
    setAccount(AccountValue);
  };

  return (
    <section className="filter-container mb-3">
      <div className="form-row">
        <div className="col-auto">
          <CompaniesDropdown change={e => handleClientChange(e)} />
        </div>
        <div className="col-auto">
          <LocationsDropdown
            selectedCompany={company}
            change={e => handleLocationsChange(e)}
          />
        </div>
        <div className="col-auto">
          <AccountsDropdown
            selectedCompany={company}
            change={e => handleAccountsChange(e)}
          />
        </div>
        <div className="col-auto">
          <Search />
        </div>
      </div>
    </section>
  );
};

InvoiceFilters.getInitialProps = async ({ req }) => {
  const isServer = !!req;
  return { isServer };
};

export default InvoiceFilters;

还有数据列表

import React from "react";
import Link from "next/link";
import InvoiceListRecord from "./InvoiceListRecord";

const InvoiceList = props => {
  let dataCollection = props.data.data;

  return dataCollection.length == 0 ? "" : dataCollection.map((item, index) => {
    return (
      <section key={"item-" + index} className="card text-left mb-3">
        <header className="card-header">
          <span className="pr-1">{props.data.titleHeader}</span>
          <Link
            href={
              "/" +
              props.data.childPathRoot +
              "?invoiceId=" +
              item[props.data.childIdField]
            }
            as={
              "/" +
              props.data.childPathRoot +
              "/" +
              item[props.data.childIdField]
            }
          >
            <a>{item[props.data.titlefield]}</a>
          </Link>{" "}
        </header>
        <div className="card-body">
          <div className="row">
            <InvoiceListRecord
              data={item}
              childDataCollection={props.data.childDataCollection}
            />
          </div>
        </div>
      </section>
    );
  });
};

InvoiceList.getInitialProps = async ({ req }) => {
  console.log("Get Intitial Props works: Invoices Page!");
  const isServer = !!req;
  return { isServer };
};

export default InvoiceList;

和列表项组件。

import React from "react";

const InvoiceListRecord = props => {
  var invoiceData = JSON.parse(props.data[props.childDataCollection]);

  return invoiceData.map((invKey, index) => {
    return (
      <div className="col-3 mb-1" key={"item-data-" + index}>
        <strong>{invKey.MappedFieldName}</strong>
        <br />
        {invKey.Value}
      </div>
    );
  });
};

export default InvoiceListRecord;

【问题讨论】:

    标签: reactjs axios react-hooks


    【解决方案1】:

    如果 url 相同,则不会多次调用 API。它只是从data 变量中获取值。不会再次调用 api,除非 url 发生变化。

    我根据您的代码创建了一个示例,将所有未知组件更改为 div。我在useAxiosFetch 钩子的useEffect 中添加了console.log。为了重新渲染组件,我添加了一个按钮来增加计数。

    您将看到来自钩子的 console.log 仅打印一次,即使每次单击按钮时组件都会重新呈现。该值仅来自挂钩中的data 变量,并且不会一次又一次地进行api调用。

    【讨论】:

    • 也许我应该为您提供更多组件以便您更好地测试。我可以向你保证,当我在 api 中放置断点时,它会调用 api 两次,并且它会两次命中该方法。
    • 另外,你是对的。网址没有改变。他们来了。 localhost:44394/Utilities/GetCompaniesuseAxiosFetch.js:55 localhost:44394/Invoice/GetInvoices/falseFetchDropdownData.js:55 localhost:44394/Utilities/GetCompanies
    • 很难理解您的代码,其中包含我无法在不知道您在做什么的情况下填补的所有漏洞。如果您可以将整个代码添加到代码沙箱或给我发送一个 git repo,我可以检查一下。
    猜你喜欢
    • 2021-09-29
    • 1970-01-01
    • 2020-03-08
    • 2022-01-05
    • 2019-06-15
    • 1970-01-01
    • 2020-05-18
    • 2022-07-18
    • 2020-11-13
    相关资源
    最近更新 更多