0

Say I have a React component which has some async function, like a setTimeOut function which performs a task on the DOM element of that component. If the component unmounts during that setTimeOut, how would I prevent that async function from firing? The resulting function that fires throws an error since the DOM element no longer exists due to the component unmounting.

I'm attempting to ask a general question about theoretical ways to go about this, since I have quite a specific—but quite uncommon—use-case that essentially got no views in a previous question. The specific use-case that I have involves calling destroy() on a pixi-react-fiber graphics component after an animation completes, when sometimes the component has been unmounted during the animation. I think the general form of this question should be able to help me understand an approach.

edit: adding example

Container.js

const CursorContainer = props => {
  const [darkMode, setDarkMode] = useState(props.darkMode)

  useEffect(() => {
    setDarkMode(props.darkMode)
  }, [props.darkMode])

  return (
    <Cursor darkMode={darkMode} />
  )
}

export default CursorContainer

Cursor.js

const TYPE = "Cursor"

export const behavior = {
  customDisplayObject: props => new PIXI.Graphics(),
  customApplyProps: function (g, _, props) {
    const { darkMode } = props
    g.clear()
    g.beginFill(darkMode ? 0x7225e6 : 0xd3fc03, 1)
    g.drawCircle(0, 0, 20)
    g.endFill()
    setTimeout(() => g.destroy(), 5000)
  },
}

const Cursor = CustomPIXIComponent(behavior, TYPE)

export default Cursor

If the darkMode is toggled, the Cursor component will be unmounted and remounted due to the state change. If this happens during the 5s setTimeOut, it will throw an error, since the graphics DOM element of the component will be gone and there will be nothing to destroy. How do I prevent this function from firing if the component has been unmounted?

2
  • May you share a minimal reproducible example which shows the issue? Commented Oct 21, 2020 at 16:56
  • added example in edit Commented Oct 21, 2020 at 17:14

4 Answers 4

1

Use a ref for maintaining a reference to the interval, and then clear the interval in the effect clean up callback -

e.g.

const Component = () => {
    const intervalRef = useRef(null);
    
    useEffect(() => {
        intervalRef.current = setInterval(() => {}, 1000);
        
        return () => {
            clearInterval(intervalRef.current);
        }
    }, [])
}

The use of ref will allow you to clear the timeout/interval from other handlers in the component

edit...

based on your response, if you were to update to the following, to return the timeout -

export const behavior = {
  customDisplayObject: props => new PIXI.Graphics(),
  customApplyProps: function (g, _, props) {
    const { darkMode } = props
    g.clear()
    g.beginFill(darkMode ? 0x7225e6 : 0xd3fc03, 1)
    g.drawCircle(0, 0, 20)
    g.endFill()
    return setTimeout(() => g.destroy(), 5000)
  },
}

then whereever in you Cursor component, that you have access to customApplyProps you could get the setTimeout reference from it, e.g.

intervalRef.current = customApplyProps();

then in an effect, clean it up in the return callback -

useEffect(() => {
    return () => {
        clearInterval(intervalRef.current);
    }
}, [])
Sign up to request clarification or add additional context in comments.

3 Comments

On second thought, I probably did need a simple example of my use-case, since the component is not a function or class based react component, but a CustomPIXIComponent from react-pixi-fiber. You can see my edit in the OP.
depending on how you using customApplyProps, you could try changing the last line of that function to - return setTimeout(() => g.destroy(), 5000) so that whatever is calling it now has a ref to the timeout, then do as I have done above to clear the timeout when CustomPIXIComponent unmounts? e.g. intervalRef.current = customApplyProps() or whatever
Forgive my being dense but I'm not totally sure I understand. Are you saying that returning the setTimeOut would give ref access to the parent? Do you think you could edit your answer?
0

I think a simpler approach here would be to lift the function up to a parent component which will not unmount. Then you can have a piece of state in the parent that controls whether the child component is rendered, and use that state as a logic gate when the function is invoked.

4 Comments

I'm not sure how I could lift the function to a parent component since it is a method of that specific CustomPIXIComponent... Unless I am misunderstanding what you're saying.
You don't have to lift the function. You can pass a call back function down to the props and then pass the g object to the function. const { darkMode, parentCallBackDestory } = props ... setTimeout(() => parentCallBackDestory(g), 5000). Your parent then has the "logic gate" in the parentCallBackDestory
Yes I see, what would be the best sort of logic gate in this situation. I'm reading that isMounted is antipattern according to react docs.
Trying to keep it simple, your CustomPIXIComponent needs to access to your "g" object. So when React component unmounts naturally in the unmounting step you can call g.destroy(). ... Not sure what your CustomPIXIComponent looks like, but if it's using a hook then in the return statement return () => { g.destroy() }; .... The point is CustomPIXIComponent needs to handle when to destory g, not your customApplyProps function. This will avoid the antipattern.
0

I had a simmilaire situaton and this how I solved it :

const CursorContainer = props => {

  const [darkMode, setDarkMode] = useState(props.darkMode)

  useEffect(() => {
   let mounted= true; 
   if(mounted) setDarkMode(props.darkMode)
    return () => { mounted=false ; }
  }, [props.darkMode])

  return (
    <Cursor darkMode={darkMode} />
  )
}

export default CursorContainer

Comments

0

If you are using function components, you can return a cleanup function in the useEffect hook that automatically gets called when the component unmounts. You can take the clearTimeout function that the setTimeout returns and call it in that cleanup function.

  useEffect(() => {
    const timeout = setTimeout(..., ...);

    return () => {
      clearTimeout(timeout);
    };
  }, []);

1 Comment

I've added a simplified example in my OP, the component is actually a CustomPIXIComponent which obviously does not allow hooks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.