Method call callback setting state on unmounted component

I’m using React. Say I have 2 components, Parent and Child. Child is mounted within Parent. Child contains ProblemFunction, which calls FunctionOnParent on Parent (via a prop), and then makes a Meteor Method Call. The asynchronous callback function for the method sets state on Child. However, sometimes, the FunctionOnParent causes Child to get unmounted. This means that when the method call returns and executes the callback, it attempts to set state on an unmounted component, causing a warning about setting state on an unmounted component.

Here’s some pseudocode:

Child extends React.component {
  ProblemFunction() {
    this.props.FunctionOnParent();
    Meteor.call("SomeMethod", (err, res) => { this.setState({something:something})}
  }
}

Parent extends React.component {
  FunctionOnParent() {
   sometimes unmount Child
  }
}

Now it looks like you can’t abort method calls the way you’d cancel a timer in javascript.
Manually tracking whether the component is currently mounted and checking that before setting the state is a workaround but an antipattern according to https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html because it is a sign of memory leaks.

So how would you recommend one fix this problem?

That does not necessarily mean a memory leak but a possible indication that there might be one.

It also mentions:

An easy migration strategy for anyone upgrading their code to avoid isMounted() is to track the mounted status yourself. Just set a _isMounted property to true in componentDidMount and set it to false in componentWillUnmount, and use this variable to check your component’s status.

which is a perfectly valid solution:

class Child extends React.component {
  componentDidMount() {
    this._isMounted = true;
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  ProblemFunction() {
    this.props.FunctionOnParent();
    Meteor.call("SomeMethod", (err, res) => {
      if (this._isMounted) this.setState({ something })
    });
  }
}

But yes, it is arguably the more optimal solution to be able to cancel the callback. Unfortunately, meteor methods cannot be cancelled.

You can however create a wrapper utility function that calls a meteor method and returns a handle function that could cancel the call to the callback function,

import { makeCancellableMethodCall } from '/imports/utils';

class Child extends React.component {
  componentWillUnmount() {
    this.cancelMecancelMethodCallFromProblemFunctionthodCall && this.cancelMethodCallFromProblemFunction();
  }

  ProblemFunction() {
    this.cancelMethodCallFromProblemFunction = makeCancellableMethodCall("SomeMethod", (err, res) => { this.setState({ something }))
  }
}

but that would not be a true cancellation, nor would it be worth it if you’re not hitting this use case very often becaue there are a lot of things you need to handle within that utility function.

1 Like

@neilsusername I figured you or someone else might want to take a stab at a similar thing so here’s something to begin with:

function makeCancellableMethodCall(...args) {
  if (!Meteor.isClient) throw new Error('use synchronous style in the server, this is client only')
  if (!args || args.length < 2) throw new Error('must at least provide name and callback')

  const cb = args.pop()

  if (typeof args[0] !== 'string') throw new Error('first argument must be method name')
  if (typeof cb !== 'function') throw new Error('callback must be a function')

  let isCancelled = false

  Meteor.call(...args, (err, res) => {if (!isCancelled) cb(err, res)})

  return function() {
    isCancelled = true;
  }
}

const cancelMe = makeCancellableMethodCall('soruFavoriBosalt', (e,r) => console.log({e,r}))

// cancelMe() to cancel the execution of the callback, beware though that the method still executes and returns the result to the client

OK, thanks @serkandurusoy for taking a stab at defining the utility function. I guess I’ll just go with rolling my own isMounted for now, since this is just a hack I’m working on. I’m surprised that more people haven’t run into this case though, given that React now seems to be promoted as the preferred front-end system for Meteor.

1 Like

Hey @neilsusername did you ever figure out a more optimal solution here? I’m running into this myself.

We just created an HOC which has the _isMounted property. The HOC adds a callIfMounted() method which executes the callback if still mounted

1 Like

@rjdavid cool, interesting approach. thanks for sharing.

This would work

Child extends React.component {
  ProblemFunction() {

    let comp = this;    

    Meteor.call("SomeMethod", (err, res) => { comp.setState({something:something})}

  }
}