Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow react components' lifecycle methods to be async #1803

Closed
jakpaw opened this issue May 17, 2016 · 13 comments
Closed

Allow react components' lifecycle methods to be async #1803

jakpaw opened this issue May 17, 2016 · 13 comments
Labels

Comments

@jakpaw
Copy link

jakpaw commented May 17, 2016

I tried to declare componentWillMount in a component as async but got a flow error: Promise This type is incompatible with undefined. I found out that it's caused by this definition:

componentWillMount(): void;

in https://github.com/facebook/flow/blob/master/lib/react.js

Shouldn't it be possible for components' lifecycle methods which return void to return a Promise instead?

@Macil
Copy link
Contributor

Macil commented Jun 16, 2016

I find the strictness about void functions useful as it's caught lots of cases where I've misunderstood a method or callback and tried returning what I thought was an important value.

Or if the React definitions were changed to have the return type of void|Promise, I think that would imply that React handled promises specially. I just had to double-check that wasn't the case.

You could still do async stuff inside a void returning function by using an immediately-invoked async function:

class X extends React.Component {
  componentWillMount() {
    (async ()=>{
      console.log('foo');
      await delay(123);
      console.log('bar');
    })();
  }
}

@brentvatne
Copy link

I agree with the original post, I think flow should let us do async lifecycle functions in React. See https://twitter.com/Vjeux/status/772202716479139840

@brentvatne
Copy link

cc @thejameskyle :>

@andreypopp
Copy link
Contributor

andreypopp commented Sep 4, 2016

Or if the React definitions were changed to have the return type of void|Promise, I think that would imply that React handled promises specially.

On the other hand, if React doesn't care about returned value shouldn't it be *?

@vkurchatkin
Copy link
Contributor

I think this could actually be misleading, like react is actually going to wait until promise is resolved

@rpominov
Copy link

rpominov commented Sep 4, 2016

https://twitter.com/Vjeux/status/772202716479139840

I think this isn't actually a great idea, even without issue with Flow. We should unsubscribe / cancel these async operations in componentWillUnmount. Also person who reads async componentDidMount may assume that React does something with returned promise.

So maybe Flow shouldn't encourage such pattern.

@samwgoldman
Copy link
Member

samwgoldman commented Sep 4, 2016

There's precedent in callback functions to annotate the callback as mixed when the caller doesn't care. The justification there is that () => e may be used solely for the side-effects of e. If e has a return value, it doesn't hurt anything to return it. This lifecycle method is a callback in spirit, as far as I can tell, so any reasoning that applies to callbacks should apply here.

I'm of two minds here, really. On one hand, I think Flow should concern itself with type errors primarily and avoid too much hand-holding. If something works at runtime, we should have a good reason to make it a type error.

On the other hand, I personally believe that () => void is an useful signifier of "side effects are happening here" and changing that to () => mixed communicates poorly by not guiding developers away from useless code like [1, 2, 3].forEach(x => x + 1). For my own code, I would use () => void, but I understand why the community decided to go the other way.

So I think there's some justification for changing the return type from void to mixed.

Another option is to change the return type to void | Promise<void>, but I don't like that idea. That type signature gives the impression that React will work to make async side effects safe, but it won't. I think it's misleading / doesn't represent the "actual" types.

@vkurchatkin
Copy link
Contributor

One problem with using async function as callback, is that it's easy to forget, that rejections will be ignored. If callback is () => void, you are forced to think about it.

There's precedent in callback functions to annotate the callback as mixed when the caller doesn't care

This could also be unsafe in context of lib defs. In future versions of a library returning something other than undefined could have a special meaning which could lead to unexpected behaviour.

@echenley
Copy link

echenley commented Feb 13, 2017

Probably worth mentioning that this is slightly annoying for tests. If you want to do something like:

componentDidMount() {
  return doSomething().then(() => {
    this.setState({ some: 'state' })
  })
}

An ideal enzyme test would be something like this:

it('fetches something on componentDidMount then sets state', async () => {
  const component = shallowRenderComponent()
  await component.instance().componentDidMount()
  expect(component.state()).toBe({ some: 'state' })
})

But because of this flow restriction, you instead have to split out the async logic into another testable method:

componentDidMount() {
  this.doSomething()
}

doSomething() {
  return doSomething().then(() => {
    this.setState({ some: 'state' })
  })
}

And the test becomes:

it('calls this.doSomething on mount', () => {
  const component = shallowRenderComponent()
  const doSomething = jest.fn()
  component.instance().doSomething = doSomething
  component.instance().componentDidMount()
  expect(doSomething).toHaveBeenCalled()
})

it('doSomething fetches something then sets state', async () => {
  const component = shallowRenderComponent()
  await component.instance().doSomething()
  expect(component.state()).toBe({ some: 'state' })
})

Not a deal breaker, but kind of a burden for seemingly not much gain.

@sibelius
Copy link

any workaround for this?

@akoufa
Copy link

akoufa commented Aug 30, 2017

Hello and thank you for the flow type checker. I would like to know if there is a solution for this issue or a workaround?

@vkurchatkin
Copy link
Contributor

Here is the workaround:

class X extends React.Component<{}> {
  componentWillMount() {
    this._componentWillMount();
  }
  
  async _componentWillMount() {
   
  }
}

@Palisand
Copy link

Are there any negative consequences to using suppressions like $FlowFixMe instead of an IIFE (@agentme) or a sub-function (@vkurchatkin)? Wouldn't this be the easiest and fastest workaround provided you have already setup your .flowconfig to support it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.