Set State only once

Viewed 10516

I am using a function (getActorInfo()) in react to grab info from an api and set that in a State. It works but the function wont stop running.

export default function ActorProfile({ name, img, list, id, getActorInfo }) {
  const [showList, setShowList] = useState(false);
  const [actorInfo, setActorInfo] = useState({});

  getActorInfo(id).then(val => setActorInfo(val));

  console.log(actorInfo)

  return (
    <Wrapper>
      <Actor
        id={id}
        name={name}
        img={img}
        onClick={() => {
          setShowList(!showList);
        }}
        actorBirthday={actorInfo.actorBirthday}
      />
      {showList && <MovieList list={list} actorInfo={actorInfo} />}
    </Wrapper>
  );
}

I tried using useEffect like this

  useEffect(() => {
    getActorInfo(id).then(val => setActorInfo(val));
  }, {});

But I get an error that I do not understand

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 ActorProfile (at App.js:60)

My question is how to have this function only run once?

3 Answers

Anything in a functional component body will run every render. Changing to a useEffect is the correct solution to this problem.

It isn't working for you because useEffect takes an array as its second parameter, not an object. Change it to [], and it will only run once.

useEffect(() => {
  getActorInfo(id).then(val => setActorInfo(val));
}, []);

This will be equivalent to the class-based componentDidMount.

If your hook has a dependency, you add it to the array. Then the effect will check to see if anything in your dependency array has changed, and only run the hook if it has.

useEffect(() => {
  // You may want to check that id is truthy first
  if (id) {
    getActorInfo(id).then(val => setActorInfo(val));
  }
}, [id]);

The resulting effect will be run anytime id changes, and will only call getActorInfo if id is truthy. This is an equivalent to the class-based componentDidMount and componentDidUpdate.

You can read more about the useEffect hook here.

You are still not checking if the component is mounted before you set the state. You can use a custom hook for that:

const useIsMounted = () => {
  const isMounted = useRef(false);
  useEffect(() => {
    isMounted.current = true;
    return () => (isMounted.current = false);
  }, []);
  return isMounted;
};

Then in your component you can do:

const isMounted = useIsMounted();

useEffect(() => {
  getActorInfo(id).then(
    val => isMounted && setActorInfo(val)
  );
}, [getActorInfo, id, isMounted]);

you need to cleanup useEffect like

useEffect(() => {
    getActorInfo(id).then(val => setActorInfo(val));

    return () => {
        setActorInfo({});
    }
},[]);

have a look at this article. It explains you why to cleanup useEffect.

Related