Search code examples
reactjsuse-effectcodemirror

React useEffect hooks return () => cleanup() vs return cleanup


I have a question about useEffect's cleanup callback function. In one word, return cleanup gives an error, but return () => cleanup() works well. Yes, they are different, but how are they different? Does anyone know how to explain? Thanks,

I came across this question while I am writing with codemirror.

The error message I see is

Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'plugins')

My hooks

export const useCodeMirror = () => {
  const state = ...
  const parent = ...
  
  useEffect(() => {
    const view = new EditorView({state: state, parent: parent})
    // return view.destroy raises an error
    return () => view.destroy()  // works perfectly
  }, [element])
}

node_modules/@codemirror/view/dist/index.js L:6740-6750

    destroy() {
        for (let plugin of this.plugins)
            plugin.destroy(this);
        this.plugins = [];
        this.inputState.destroy();
        this.dom.remove();
        this.observer.destroy();
        if (this.measureScheduled > -1)
            cancelAnimationFrame(this.measureScheduled);
        this.destroyed = true;
    }

package.json

{
  "dependencies": {
    ...
    "codemirror": "^6.0.1",
    ...
  }
}

Solution

  • It's normally fine to return a cleanup function directly, rather than wrapping it in an additional arrow function. The only reason the additional function is needed in this case because destroy uses this.

    For regular functions, the value of this is determined by how the function is called. So if you have code that says view.destroy(), then the characters view. are the reason that this gets set to view. That's why () => view.destroy() one works: you are explicitly saying what this should be, as you call the function.

    But if you just return view.destroy, you are not calling the function, just returning a reference of it to react. React doesn't know anything about view, it just knows you returned a function. So when react later calls your function, it doesn't know what to set this to, and so this gets set to undefined. Since it's undefined, this.plugins causes the error you see.