Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useEffect cleanup function depends on async await result

I have a react component which gets some resource asynchronously and then subscribes to resource changes.

The problem is that the cleanup function is not in the closure of this resource:

useEffect(() => {
  const onResourceChange = () => { 
    console.log('resource changed');
  };

  getSomeResource().then(resource => {
    resource.subscribe(onResourceChange);
  });

  return () => {
    resource.unsubscribe(onResourceChange); // Error! resource is undefined
  }
}, []);

Since using async functions in useEffect is not allowed, what is the best way to unsubscribe from this resource inside useEffect's cleanup function?

like image 894
Lior Erez Avatar asked Sep 20 '25 19:09

Lior Erez


2 Answers

I see two "side effects" going on here.

  1. Getting the resource (need happen once, dependency [])
  2. Subscribe/unsubscribing the callback (needs to happen on resource change, dependency, [resource])

So I

  1. separated them into two "effects" (steps, each handling one side effect)
  2. and extract it into a custom hook
    function useResource() {
        const [resource, setResource] = useState(undefined)

        const onResourceChange = () => console.log('resource changed');

        // Get the resource, initially.
        useEffect(() => {
          getSomeResource(setResource)
        }, [])

        // When the resource is retrieved (or changed),
        // the resource will subscribe and unsubscribe
        useEffect(() => {
          resource.subscribe(onResourceChange)
          return () => resource.unsubscribe(onResourceChange)
        }, [resource])
    }

    // Use it like this
    function App() {
        useResource()

        return <>your elements</>
    }

As you can see, the first useEffect is responsible for fetching the "resource" only, while the 2nd useEffect is responsible for un/subscribing the callback.

And check out the deps list in each useEffect (The former is empty [] while the latter depends on [resource])

like image 57
dance2die Avatar answered Sep 22 '25 10:09

dance2die


This is a solution, probably the ugly one... but still a solution :)

useEffect(() => {
    const onResourceChange = () => { 
        console.log('resource changed');
    };

    let data = {};

    getSomeResource().then(resource => {
        data.resource = resource;
        resource.subscribe(onResourceChange);
    });

    return () => {
        data.resource && data.resource.unsubscribe(onResourceChange); // Error! resource is undefined
    };
}, []);
like image 31
farvilain Avatar answered Sep 22 '25 08:09

farvilain