自定义反应挂钩触发api调用多次

时间:2019-09-11 02:01:04

标签: reactjs axios react-hooks

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

我正在使用自定义的react钩子和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;

1 个答案:

答案 0 :(得分:0)

如果URL相同,则不会多次调用API。它只是从data变量中获取值。除非url更改,否则不会再次进行api调用。

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

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

Edit loving-cartwright-03y8h