Source:  Twitter logo

useEffect fires perfectly each time I change the route, When I call the API from within useEffect, and then try to set state with the result I get the error 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.

I have tried invoking getPrice with a self-invoking function and nothing changes, I still get the same error.

Should I be using 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 };

This calc component gets loaded in the router like this

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>
  );
}

I can't tell any obvious problem from the code you share. But the error says that, when setPrice({ priceUsd }) is called, the <Calc /> component is already unmounted.

So the problem is elsewhere, its parent component un-renders the <Calc /> before completion of fetch logic.

I propose a method to verify, see (+/-) sign for 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 };

If my theory is correct, we expect to see "I got cleaned-up" printed in console first before "calling setPrice()"

2 users liked answer #0dislike answer #02
hackape profile pic
hackape

I had the same problem, the thing what you need to do is write the function outside the useEffect and call that function, some thing like this.

 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();
  }, []);
1 users liked answer #1dislike answer #11
coderLogs profile pic
coderLogs

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).