6

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?

Jkaram
  • 611
  • 2
  • 7
  • 13

3 Answers3

7

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.

Brian Thompson
  • 13,263
  • 4
  • 23
  • 43
  • 3
    `id` is a prop, so `id` should be in the array (e.g. when `id` changes, change `actorInfo`). – crashmstr Mar 10 '20 at 15:41
  • 1
    Don't count on `useEffect` to be satisfied with an empty array though. If not providing it with the appropriate depts unexepcted behavior may occur. This is a great read if you have an extra minute: https://overreacted.io/a-complete-guide-to-useeffect/ – faerin Mar 10 '20 at 15:56
  • Adding these as dependencies worked [id, getActorInfo] Thanks! – Jkaram Mar 10 '20 at 16:38
3

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]);
HMR
  • 37,593
  • 24
  • 91
  • 160
1

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.

slartidan
  • 20,403
  • 15
  • 83
  • 131