React TypeScript:每次路由更改时如何调用 api 并设置状态

React TypeScript: How to call an api and set state each time the route changes

useEffect 每次更改路线时都会完美触发,当我从 useEffect 中调用 API,然后尝试使用结果设置状态时,我得到错误 Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

我试过用自调用函数调用 getPrice 但没有任何改变,我仍然得到同样的错误。

我应该使用 Suspense ??

import React, { useContext, useEffect, useState } from 'react';
const Calc: React.FC = () => {

  interface StateInterface {
    priceUsd: number;
  }

  const [price, setPrice] = useState<StateInterface>({
    priceUsd: 0,
  });

  useEffect(() => {
    const getPrice = async () => {
      const response = await fetch('http://localhost:9999/price', {
        body: JSON.stringify({
          jwtToken: localStorage.getItem('jwtToken'),
        }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        method: 'POST',
      });
      if (response.status !== 400) {
        const content = await response.json();
        const priceUsd = content.price[0].priceUsd;
        setPrice({ priceUsd });
      }
    };
    getPrice();
  }, []);

  return (
    <div>Calc</div>
  )
}
export { Calc };

这个计算组件像这样加载到路由器中

import React, { useReducer } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { globalContext, setGlobalContext } from './components/shell/context';
import { Layout } from './components/shell/layout';
import { defaultState, globalReducer } from './components/shell/reducer';
import { Calc } from './routes/calc';
import { NotFound } from './routes/not-found';

export function Router(): JSX.Element {
  const [global, setGlobal] = useReducer(globalReducer, defaultState);

  return (
    <setGlobalContext.Provider value={{ setGlobal }}>
      <globalContext.Provider value={{ global }}>
        <BrowserRouter>
          <Route
            render={({ location }) => (
              <Layout location={location}>
                <Switch location={location}>
                  <Route exact path='/' component={Calc} />
                  <Route component={NotFound} />
                </Switch>
              </Layout>
            )}
          />
        </BrowserRouter>
      </globalContext.Provider>
    </setGlobalContext.Provider>
  );
}

我遇到了同样的问题,你需要做的是在 useEffect 之外编写函数并调用该函数,就像这样。

 const getPrice = async () => {
      const response = await fetch('http://localhost:9999/price', {
        body: JSON.stringify({
          jwtToken: localStorage.getItem('jwtToken'),
        }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        method: 'POST',
      });
      if (response.status !== 400) {
        const content = response.json();
        const priceUsd = content.price[0].priceUsd;
        setPrice({ priceUsd });
      }
    };

useEffect(() => {
    getPrice();
  }, []);

我无法从您分享的代码中看出任何明显的问题。但是错误表明,当调用 setPrice({ priceUsd }) 时,<Calc /> 组件已经卸载。

所以问题出在别处,它的父组件在获取逻辑完成之前取消呈现 <Calc />

我提出一个验证方法,diff见(+/-)符号:

import React, { useContext, useEffect, useState } from 'react';
const Calc: React.FC = () => {

  interface StateInterface {
    priceUsd: number;
  }

  const [price, setPrice] = useState<StateInterface>({
    priceUsd: 0,
  });

  useEffect(() => {
    const getPrice = async () => {
      const response = await fetch('http://localhost:9999/price', {
        body: JSON.stringify({
          jwtToken: localStorage.getItem('jwtToken'),
        }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        method: 'POST',
      });
      if (response.status !== 400) {
        const content = await response.json();
        const priceUsd = content.price[0].priceUsd;
-       setPrice({ priceUsd });
+       console.log('calling setPrice()', priceUsd);
      }
    };
    getPrice();

+   return () => { console.log('I got cleaned-up') }
  }, []);

  return (
    <div>Calc</div>
  )
}

export { Calc };

如果我的理论是正确的,我们希望在 "calling setPrice()"

之前先在控制台中看到 "I got cleaned-up"