简体   繁体   中英

How to solve "can't perform a react state update on an unmounted component"

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. in PokemonListItem (at PokemonList.jsx:148)

Okay so I know this is a common issue and the solution should be quite simple. I just don't know how to implement it to my code.

I'm making a kind of Pokédex for mobile using React-Native and PokéAPI. I'm not sure where the leak lies, so more experienced developers, please help.

PokemonListItem

export default function PokemonListItem({ url, Favorite }) {
  const [pokemondata, setData] = React.useState({});
  const [dataReady, setReady] = React.useState(false);
  const [isFavorite, setFavorite] = React.useState(false);

  const favoriteStatus = (bool) => {
    setFavorite(bool);
  };

  const getData = async () => {
    await fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data));

    setReady(true);
  };

  React.useEffect(() => {
    getData();
  }, []);

  more code...

PokemonList

const renderItem = ({ item }) => (
    <TouchableHighlight
      style={{ borderRadius: 10 }}
      underlayColor="#ffc3c2"
      onPress={() => {
        navigation.navigate("Pokémon Details", {
          url: item.url,
        });
      }}
    >
      <PokemonListItem url={item.url} Favorite={FavoriteButton} />
    </TouchableHighlight>
  );

if you need to see the full code, you can visit the repository .

An approach seems to be to maintain a variable to see whether or not the component is still mounted or not, which feels smelly to me ( React-hooks. Can't perform a React state update on an unmounted component )- but anyway this is how I would see it in your code...

   let isMounted;

   const getData = async () => {
     await fetch(url)
      .then((res) => res.json())
      .then((data) => { if(isMounted) setData(data)});

     setReady(true);
  };

  React.useEffect(() => {
    isMounted = true;
    getData();
    return () => {
      isMounted = false;
    }
  }, []);

Try this

 React.useEffect(() => { (async function onMount() { await fetch(url) .then((res) => res.json()) .then((data) => setData(data)); setReady(true); })(); }, []);

Similar to what was mentioned earlier, the key point being wrapping your state update setReady() in an if (mounted){} block .

  1. Create local variable to represent your initial mounted state let mounted = true; in your effect that has the async call
  2. Use the cleanup effect https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect to set mounted to false return () => { mounted = false }
  3. Wrap the setState call with if (mounted) { setState(...)}
useEffect(() => {
  let mounted = true;
  const apiRequest = async (setReady) => {
     let response;
     try {
        response = await APICall();
        if (mounted) {
          setReady(response.data);
        }
     } catch (error) {}
  }
  apiRequest();
  return () => { mounted = false;}
})

https://codesandbox.io/s/upbeat-easley-kl6fv?file=/src/App.tsx

If you remove the || true || true call and refresh you'll see that the error for mem leak is gone.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM