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

react-redux connect revamp #1731

Merged
merged 19 commits into from Mar 5, 2018

Conversation

@villesau
Contributor

villesau commented Jan 14, 2018

This is still very WIP and the API is not yet complete (API is now decent), so consider this more as a preview. For basic use cases with class based components these types works like a charm already (see tests).

Motivation for the rewrite is that currently connect libdefs are in really bad shape and doesn't catch if some of the required props are missing.

The main problem with this approach is that functional stateless components does not work like expected with this, see: facebook/flow#5652 E: sorted out, now they works!

Second problem is that exact object types are hard to get work with this.

@GAntoine am I missing something or is there a bug in Flow with either in $Diff or react stateless functional component type definitions? E: Needed to use React.ElementConfig

E: This starts to be ready from my side.

Show outdated Hide outdated definitions/npm/react-redux_v5.x.x/flow_v0.62.x-/react-redux_v5.x.x.js
};
declare type Null = null | void;

This comment has been minimized.

@villesau

villesau Jan 14, 2018

Contributor

My changes starts below

@villesau

villesau Jan 14, 2018

Contributor

My changes starts below

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Jan 14, 2018

Contributor

Now when I made a bit of code archeology I found out that these types have been actually using $Diff before too. This PR moved from $Diff to $SuperType: #318
@gcanti do you remember the reasoning behind it?

Contributor

villesau commented Jan 14, 2018

Now when I made a bit of code archeology I found out that these types have been actually using $Diff before too. This PR moved from $Diff to $SuperType: #318
@gcanti do you remember the reasoning behind it?

@cprodescu

This comment has been minimized.

Show comment
Hide comment
@cprodescu

cprodescu Feb 3, 2018

I'm thinking about taking a stab at this as well. For me react-redux not working consistently is one of the main detractors to recommending flow more widely.
@villesau Are you still working on this? Do you have any updates?

Thank you!

cprodescu commented Feb 3, 2018

I'm thinking about taking a stab at this as well. For me react-redux not working consistently is one of the main detractors to recommending flow more widely.
@villesau Are you still working on this? Do you have any updates?

Thank you!

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 4, 2018

Contributor

@cprodescu yes I'm still working on this :) I've been busy with other things so haven't had time to push this forward. Next i'm going to implement mergeProps. Then i think it starts to be in decent shape. Note that the libdefs won't work with stateless functional components due to some Flow bug. I'd hope to get that fixed.

Contributor

villesau commented Feb 4, 2018

@cprodescu yes I'm still working on this :) I've been busy with other things so haven't had time to push this forward. Next i'm going to implement mergeProps. Then i think it starts to be in decent shape. Note that the libdefs won't work with stateless functional components due to some Flow bug. I'd hope to get that fixed.

@ancestorak

This comment has been minimized.

Show comment
Hide comment
@ancestorak

ancestorak Feb 6, 2018

@villesau What does "won't work" mean? Will it leave things as they are or break them further?

ancestorak commented Feb 6, 2018

@villesau What does "won't work" mean? Will it leave things as they are or break them further?

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 6, 2018

Contributor

@ancestorak It means that stateless components will not type check, you don't get any errors. So you can use stateless components but you don't get any help from Flow. See the issue and examples there: facebook/flow#5652

Contributor

villesau commented Feb 6, 2018

@ancestorak It means that stateless components will not type check, you don't get any errors. So you can use stateless components but you don't get any help from Flow. See the issue and examples there: facebook/flow#5652

@cprodescu

This comment has been minimized.

Show comment
Hide comment
@cprodescu

cprodescu Feb 6, 2018

@villesau Thank you for taking this on!
Out of the following requirements, which ones do you plan to tackle? Sorry for the verbosity, want to get an idea of which cases your solution plans to address.

  1. Type-checking works when the class definition and usage are across different files.
    Current react-redux interface fails here, not trigger most ExpectError tests if you just separate current test_connect.js code into 3 files:
  • component and prop definitions
  • connector definitions
  • usage of connected components
  1. Type-checking works for class components with non-exact props
  2. Type-checking works for class components with non-exact props and default props
  3. Type-checking works for class components with exact props
  4. Type-checking works for class components with exact props and default props
  5. Type-checking works for stateless functions with non-exact props
  6. Type-checking works for stateless functions with non-exact props and default props
  7. Type-checking works for stateless functions with exact props
  8. Type-checking works for stateless functions with exact props and default props

cprodescu commented Feb 6, 2018

@villesau Thank you for taking this on!
Out of the following requirements, which ones do you plan to tackle? Sorry for the verbosity, want to get an idea of which cases your solution plans to address.

  1. Type-checking works when the class definition and usage are across different files.
    Current react-redux interface fails here, not trigger most ExpectError tests if you just separate current test_connect.js code into 3 files:
  • component and prop definitions
  • connector definitions
  • usage of connected components
  1. Type-checking works for class components with non-exact props
  2. Type-checking works for class components with non-exact props and default props
  3. Type-checking works for class components with exact props
  4. Type-checking works for class components with exact props and default props
  5. Type-checking works for stateless functions with non-exact props
  6. Type-checking works for stateless functions with non-exact props and default props
  7. Type-checking works for stateless functions with exact props
  8. Type-checking works for stateless functions with exact props and default props
@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 6, 2018

Contributor

@cprodescu I'm sorry but type definitions with exact props won't work. That's a Flow limitation: spreading exact props doesn't work right now, see facebook/flow#2405 . And also intersection between exact is impossible, e.g: {|a: number|} & {|b: number|} is impossible type: It can't match two exact types simultaneously. Thus, exact typing will not be supported any time soon at least. My approach is using intersection at least for now.

Stateless functional components will not give any errors until the bug in Flow is fixed. That's very unfortunate and my major concern with the approach. I still hope that when the types are in good enough shape otherwise, the bug will gain some traction on Flow side.

So:

  1. Type-checking works when the class definition and usage are across different files.
    Current react-redux interface fails here, not trigger most ExpectError tests if you just separate current test_connect.js code into 3 files:
  • component and prop definitions
  • connector definitions
  • usage of connected components
    • This is my number 1 goal. To get type checking working across different files by typing your functions and components props you pass to connect. And for class based components it's already the case, feel free to copy & paste the types and try out. Only mergeProps argument is missing. You can also check out the tests I've written so far, I hope they are more readable and understandable than the previous tests.
  1. Type-checking works for class components with non-exact props
    • This works
  2. Type-checking works for class components with non-exact props and default props
    • Not sure, can you give an example of this? E: works.
  3. Type-checking works for class components with exact props
    • Exact props will not work E: Works and there is a test to demonstrate
  4. Type-checking works for class components with exact props and default props
    • Exact props with default props will not work
  5. Type-checking works for stateless functions with non-exact props
    • I hope to get the bug fixed once the types are in good shape. These should work exactly like with class based components. This works
  6. Type-checking works for stateless functions with non-exact props and default props
    • example needed This works
  7. Type-checking works for stateless functions with exact props
    • Exact props will not work E: Works and there is a test to demonstrate
  8. Type-checking works for stateless functions with exact props and default props
    • Exact props with default props will not work

Would be really good if you could try out the definitions so that I get more feedback what works and what doesn't. I've tested this against our own large redux code base, but different teams might have different conventions so my current solution is aiming to solve our own use cases at first, which i hope to help others as much as possible too.

Contributor

villesau commented Feb 6, 2018

@cprodescu I'm sorry but type definitions with exact props won't work. That's a Flow limitation: spreading exact props doesn't work right now, see facebook/flow#2405 . And also intersection between exact is impossible, e.g: {|a: number|} & {|b: number|} is impossible type: It can't match two exact types simultaneously. Thus, exact typing will not be supported any time soon at least. My approach is using intersection at least for now.

Stateless functional components will not give any errors until the bug in Flow is fixed. That's very unfortunate and my major concern with the approach. I still hope that when the types are in good enough shape otherwise, the bug will gain some traction on Flow side.

So:

  1. Type-checking works when the class definition and usage are across different files.
    Current react-redux interface fails here, not trigger most ExpectError tests if you just separate current test_connect.js code into 3 files:
  • component and prop definitions
  • connector definitions
  • usage of connected components
    • This is my number 1 goal. To get type checking working across different files by typing your functions and components props you pass to connect. And for class based components it's already the case, feel free to copy & paste the types and try out. Only mergeProps argument is missing. You can also check out the tests I've written so far, I hope they are more readable and understandable than the previous tests.
  1. Type-checking works for class components with non-exact props
    • This works
  2. Type-checking works for class components with non-exact props and default props
    • Not sure, can you give an example of this? E: works.
  3. Type-checking works for class components with exact props
    • Exact props will not work E: Works and there is a test to demonstrate
  4. Type-checking works for class components with exact props and default props
    • Exact props with default props will not work
  5. Type-checking works for stateless functions with non-exact props
    • I hope to get the bug fixed once the types are in good shape. These should work exactly like with class based components. This works
  6. Type-checking works for stateless functions with non-exact props and default props
    • example needed This works
  7. Type-checking works for stateless functions with exact props
    • Exact props will not work E: Works and there is a test to demonstrate
  8. Type-checking works for stateless functions with exact props and default props
    • Exact props with default props will not work

Would be really good if you could try out the definitions so that I get more feedback what works and what doesn't. I've tested this against our own large redux code base, but different teams might have different conventions so my current solution is aiming to solve our own use cases at first, which i hope to help others as much as possible too.

Show outdated Hide outdated definitions/npm/react-redux_v5.x.x/flow_v0.62.x-/test_connect.js
function testMapDispatchToPropsPassesActionCreators() {
type Props = {
passtrough: number,
dispatch1: () => {},

This comment has been minimized.

@cprodescu

cprodescu Feb 8, 2018

This does not match the function you are passing in mapDispatchToProps: () => {}, you probably want () => void here since you are not returning an object from dispatch1

@cprodescu

cprodescu Feb 8, 2018

This does not match the function you are passing in mapDispatchToProps: () => {}, you probably want () => void here since you are not returning an object from dispatch1

This comment has been minimized.

@villesau

villesau Feb 17, 2018

Contributor

good catch! Seems that Flow cannot handle the return value of the function. Fortunately it understands what needs to be passed in, i'll add test for that.

@villesau

villesau Feb 17, 2018

Contributor

good catch! Seems that Flow cannot handle the return value of the function. Fortunately it understands what needs to be passed in, i'll add test for that.

Show outdated Hide outdated definitions/npm/react-redux_v5.x.x/flow_v0.62.x-/test_connect.js
};
const mapStateToProps = (state: State, props: InputProps) => {
return {
fromStateToProps: state.a

This comment has been minimized.

@cprodescu

cprodescu Feb 8, 2018

state.a is number, forMapStateToProps is string

@cprodescu

cprodescu Feb 8, 2018

state.a is number, forMapStateToProps is string

This comment has been minimized.

@villesau

villesau Feb 8, 2018

Contributor

that's very true, thanks for pointing out! This had totally slipped trough my eyes. Seems that the inference chain gets broken here. Same with props. If state.a is replaced with e.g 'abc', tests starts to fail like they should.

This is one of the worst parts of Flow: It's really hard to know when Flow infers something as any

Interestingly, Flow versions prior to 0.57.1 catches the error, so it's clearly a regression in Flow.

@villesau

villesau Feb 8, 2018

Contributor

that's very true, thanks for pointing out! This had totally slipped trough my eyes. Seems that the inference chain gets broken here. Same with props. If state.a is replaced with e.g 'abc', tests starts to fail like they should.

This is one of the worst parts of Flow: It's really hard to know when Flow infers something as any

Interestingly, Flow versions prior to 0.57.1 catches the error, so it's clearly a regression in Flow.

This comment has been minimized.

@villesau

villesau Feb 8, 2018

Contributor

wrote a bug report about this: facebook/flow#5792

@villesau

villesau Feb 8, 2018

Contributor

wrote a bug report about this: facebook/flow#5792

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 17, 2018

Contributor

I've now implemented first version of mergeProps. Seems to work decently.

Flow complains Cannot create MySFC element because props [1] is incompatible with empty [2].

when using stateless functional components and references to

79│ declare function connect<P: Object, R: Object, PR: Object>(

So basically Flow infers stateless functional components as empty (?) with these libdefs.

Workaround for this is to write SFC like: const MySFC: React.ComponentType<Props> = (props: Props) => <div /> which is suboptimal. The fact that Flow complains about this is better than failing silently though. But ideally I wouldn't need to write a type for stateless functional components.

Otherwise I would be OK with having to type SFCs, but it causes huge noise when taking the libdefs into use in large codebase.

Any ideas?

Contributor

villesau commented Feb 17, 2018

I've now implemented first version of mergeProps. Seems to work decently.

Flow complains Cannot create MySFC element because props [1] is incompatible with empty [2].

when using stateless functional components and references to

79│ declare function connect<P: Object, R: Object, PR: Object>(

So basically Flow infers stateless functional components as empty (?) with these libdefs.

Workaround for this is to write SFC like: const MySFC: React.ComponentType<Props> = (props: Props) => <div /> which is suboptimal. The fact that Flow complains about this is better than failing silently though. But ideally I wouldn't need to write a type for stateless functional components.

Otherwise I would be OK with having to type SFCs, but it causes huge noise when taking the libdefs into use in large codebase.

Any ideas?

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 18, 2018

Contributor

Some known issues:

  1. If your component is connected in different file where you pass mapStateToProps etc, libdefs doesn't work. You get Error:(40, 4) Cannot create 'MyComponent' element because props [1] is incompatible with empty [2]. error. Flow bug? ( #1331 )
  2. In some rare cases where property is missing, error is shown in wrong place. Instead of component usage the error is shown where mapStateToProps etc are defined.
  3. StatelessFunctionalComponents need to be separately typed like const MySFC: React.ComponentType<Props> = (props: Props) => <div /> ( facebook/flow#5652 ?) EDIT: Fixed with slight libdef changes!
  4. exact types does not work ( facebook/flow#2405 )
  5. There is a case where inference chain gets broken when passing variable straight from state to return props: ( #1731 (comment) )

But regardless of the issues, I've been able to take the current version of libdefs into use for rather large React & redux code base. Of course, that codebase has more or less unified way of using Redux with React so might be that I haven't been able to cover your use case. It's also unfortunate that SFCs needs to be typed, but on the other hand with these libdefs i've been able to catch good amount of real issues. If you write your react & redux like explained in the first list item, you might or might not be able to already get some benefit of this.

Contributor

villesau commented Feb 18, 2018

Some known issues:

  1. If your component is connected in different file where you pass mapStateToProps etc, libdefs doesn't work. You get Error:(40, 4) Cannot create 'MyComponent' element because props [1] is incompatible with empty [2]. error. Flow bug? ( #1331 )
  2. In some rare cases where property is missing, error is shown in wrong place. Instead of component usage the error is shown where mapStateToProps etc are defined.
  3. StatelessFunctionalComponents need to be separately typed like const MySFC: React.ComponentType<Props> = (props: Props) => <div /> ( facebook/flow#5652 ?) EDIT: Fixed with slight libdef changes!
  4. exact types does not work ( facebook/flow#2405 )
  5. There is a case where inference chain gets broken when passing variable straight from state to return props: ( #1731 (comment) )

But regardless of the issues, I've been able to take the current version of libdefs into use for rather large React & redux code base. Of course, that codebase has more or less unified way of using Redux with React so might be that I haven't been able to cover your use case. It's also unfortunate that SFCs needs to be typed, but on the other hand with these libdefs i've been able to catch good amount of real issues. If you write your react & redux like explained in the first list item, you might or might not be able to already get some benefit of this.

@ryami333

This comment has been minimized.

Show comment
Hide comment
@ryami333

ryami333 Feb 18, 2018

Just here to say that this refactor was well overdue and I'm very grateful that someone has decided to take this on - I've found the existing connect types almost entirely useless and the type integrity of all of my apps break down at the boundary between components and containers.

ryami333 commented Feb 18, 2018

Just here to say that this refactor was well overdue and I'm very grateful that someone has decided to take this on - I've found the existing connect types almost entirely useless and the type integrity of all of my apps break down at the boundary between components and containers.

@ryami333

This comment has been minimized.

Show comment
Hide comment
@ryami333

ryami333 Feb 20, 2018

@villesau I just smoke-tested this type-def in my current project and it works 1000x better than the existing type defs. It's uncovered dozens of potential defects I wouldn't have caught otherwise. I can't wait until these are merged! 🍾 🎉

ryami333 commented Feb 20, 2018

@villesau I just smoke-tested this type-def in my current project and it works 1000x better than the existing type defs. It's uncovered dozens of potential defects I wouldn't have caught otherwise. I can't wait until these are merged! 🍾 🎉

Show outdated Hide outdated definitions/npm/react-redux_v5.x.x/flow_v0.62.x-/react-redux_v5.x.x.js
subKey?: string
): Provider<*, *>;
declare type ConnectOptions = {

This comment has been minimized.

@harjis

harjis Feb 21, 2018

Contributor

I tested these typedefs in our project and they seemed to work a lot better than the previous ones. Thanks a lot for the effort! I can try to help out with these if I can some time next week.

ConnectOptions does not seem to be used. I think it is needed for usage like connect(mapStateToProps, null, null, { pure: false })(Component);

@harjis

harjis Feb 21, 2018

Contributor

I tested these typedefs in our project and they seemed to work a lot better than the previous ones. Thanks a lot for the effort! I can try to help out with these if I can some time next week.

ConnectOptions does not seem to be used. I think it is needed for usage like connect(mapStateToProps, null, null, { pure: false })(Component);

This comment has been minimized.

@villesau

villesau Feb 21, 2018

Contributor

Good point about options! Our project does not use them so lack of that didn't got caught :) Fortunately that should be very trivial to implement!

@villesau

villesau Feb 21, 2018

Contributor

Good point about options! Our project does not use them so lack of that didn't got caught :) Fortunately that should be very trivial to implement!

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 21, 2018

Contributor

@ryami333 Thanks for the encouraging feedback! To me these are now good enough at least for now, and we at Smartly are already using exactly this version of the libdefs :) For other usages i've been waiting for feedback and e.g options is pretty good catch already!

The test errors comes from previous libdefs, as some PRs with issues were merged when there was a period that flow-typed test suite didn't check for unused suppression.

From my side, I think I'll do some cleanup and then these should be good to go.

Contributor

villesau commented Feb 21, 2018

@ryami333 Thanks for the encouraging feedback! To me these are now good enough at least for now, and we at Smartly are already using exactly this version of the libdefs :) For other usages i've been waiting for feedback and e.g options is pretty good catch already!

The test errors comes from previous libdefs, as some PRs with issues were merged when there was a period that flow-typed test suite didn't check for unused suppression.

From my side, I think I'll do some cleanup and then these should be good to go.

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 21, 2018

Contributor

@AndrewSouthpaw could you go trough this? The interface is now decent for at least my and apparently as well for @ryami333 s use case.

The test failures comes from previous libdefs. There was a period when $ExpectError cases were not checked and some pull requests slipped trough broken.

Contributor

villesau commented Feb 21, 2018

@AndrewSouthpaw could you go trough this? The interface is now decent for at least my and apparently as well for @ryami333 s use case.

The test failures comes from previous libdefs. There was a period when $ExpectError cases were not checked and some pull requests slipped trough broken.

@sibelius

This comment has been minimized.

Show comment
Hide comment
@sibelius

sibelius Feb 23, 2018

Contributor

what is missing here?

Contributor

sibelius commented Feb 23, 2018

what is missing here?

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Feb 23, 2018

Contributor

@sibelius For my use case the libdefs are working decently, although there are some caveats e.g with SFC:s. Can you give them a try for the feedback?

Contributor

villesau commented Feb 23, 2018

@sibelius For my use case the libdefs are working decently, although there are some caveats e.g with SFC:s. Can you give them a try for the feedback?

villesau added some commits Feb 23, 2018

Support default props
Support Stateless functional components
@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Mar 3, 2018

Contributor

@ahem Actually intersection is semantically more correct than spreading in this case. If you define exact types, you should define identical (same) types for all the parts which receives the props except with mergeProps where component receives result of mergeProps only. mapStateToProps, mapDispatchToProps and component all receives the same props, with an exception that component also receives pros from mapXtoProps functions. This is already supported, you can use exact types as long as they are identical, there is also tests for that. I think exact props should represent real, exact props, when inexact props might only represent what you are interested in.

@AndrewSouthpaw I wouldn't want to add anything else into scope at this point. Any possible problems can be handled by opening a new PR. This is already much more validated PR than most of others.

Contributor

villesau commented Mar 3, 2018

@ahem Actually intersection is semantically more correct than spreading in this case. If you define exact types, you should define identical (same) types for all the parts which receives the props except with mergeProps where component receives result of mergeProps only. mapStateToProps, mapDispatchToProps and component all receives the same props, with an exception that component also receives pros from mapXtoProps functions. This is already supported, you can use exact types as long as they are identical, there is also tests for that. I think exact props should represent real, exact props, when inexact props might only represent what you are interested in.

@AndrewSouthpaw I wouldn't want to add anything else into scope at this point. Any possible problems can be handled by opening a new PR. This is already much more validated PR than most of others.

@AndrewSouthpaw

This comment has been minimized.

Show comment
Hide comment
@AndrewSouthpaw

AndrewSouthpaw Mar 5, 2018

Contributor

@villesau I think you misunderstood me, I'm with you in not wanting to add scope. :) I was making a note for others who happen to bump into the problem.

Contributor

AndrewSouthpaw commented Mar 5, 2018

@villesau I think you misunderstood me, I'm with you in not wanting to add scope. :) I was making a note for others who happen to bump into the problem.

@AndreasDahl

This comment has been minimized.

Show comment
Hide comment
@AndreasDahl

AndreasDahl Mar 5, 2018

Love the work in this PR so far, but the following code types, which i think i shouldn't

import React from 'react';

type OwnProps = {
    a: ?string,
}
                                      
type Props = {
    b: string,
}
                                      
class C extends React.Component<Props> {}
      
connect((state, ownProps : OwnProps) => ({
    b: ownProps.a,
}))(C);

AndreasDahl commented Mar 5, 2018

Love the work in this PR so far, but the following code types, which i think i shouldn't

import React from 'react';

type OwnProps = {
    a: ?string,
}
                                      
type Props = {
    b: string,
}
                                      
class C extends React.Component<Props> {}
      
connect((state, ownProps : OwnProps) => ({
    b: ownProps.a,
}))(C);
@AndrewSouthpaw

This comment has been minimized.

Show comment
Hide comment
@AndrewSouthpaw

AndrewSouthpaw Mar 5, 2018

Contributor

@AndreasDahl You can simply type the result of MSTP though. It's not perfect, but I think it's fair to keep it out of scope here. Feel free to raise a separate issue.

// @flow
import React from 'react'
import { connect } from 'react-redux'

type OwnProps = {
  a: ?string,
}

type Props = {
  b: string,
}

class C extends React.Component<Props> {}

const mapStateToProps = (state, ownProps: OwnProps): Props => ({
  b: ownProps.a,
})

const ConnectedC = connect(mapStateToProps)(C)

const a = <ConnectedC a="foo" />
Contributor

AndrewSouthpaw commented Mar 5, 2018

@AndreasDahl You can simply type the result of MSTP though. It's not perfect, but I think it's fair to keep it out of scope here. Feel free to raise a separate issue.

// @flow
import React from 'react'
import { connect } from 'react-redux'

type OwnProps = {
  a: ?string,
}

type Props = {
  b: string,
}

class C extends React.Component<Props> {}

const mapStateToProps = (state, ownProps: OwnProps): Props => ({
  b: ownProps.a,
})

const ConnectedC = connect(mapStateToProps)(C)

const a = <ConnectedC a="foo" />
@AndreasDahl

This comment has been minimized.

Show comment
Hide comment
@AndreasDahl

AndreasDahl Mar 5, 2018

@AndrewSouthpaw it looks to me like the only reason you get an error from your example is because you explicitly type return of mapStateToProps. If you instead write mapStateToProps as

const mapStateToProps = (state, ownProps: OwnProps) => ({
  b: ownProps.a,
})

Do you still get an error?

AndreasDahl commented Mar 5, 2018

@AndrewSouthpaw it looks to me like the only reason you get an error from your example is because you explicitly type return of mapStateToProps. If you instead write mapStateToProps as

const mapStateToProps = (state, ownProps: OwnProps) => ({
  b: ownProps.a,
})

Do you still get an error?

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Mar 5, 2018

Contributor

@AndreasDahl @AndrewSouthpaw I think that's a bug in Flow it self. That specific case worked before 0.57.1: facebook/flow#5792

to my knowledge, there is not much what can be done on libdef side to tackle that problem :/

Contributor

villesau commented Mar 5, 2018

@AndreasDahl @AndrewSouthpaw I think that's a bug in Flow it self. That specific case worked before 0.57.1: facebook/flow#5792

to my knowledge, there is not much what can be done on libdef side to tackle that problem :/

@AndrewSouthpaw AndrewSouthpaw merged commit dcd1531 into flow-typed:master Mar 5, 2018

1 check failed

continuous-integration/travis-ci/pr The Travis CI build failed
Details
@AndrewSouthpaw

This comment has been minimized.

Show comment
Hide comment
@AndrewSouthpaw

AndrewSouthpaw Mar 5, 2018

Contributor

Merged. I think we've explored this PR long enough. @villesau, great work, really. You've tackled a critical and buggy part of Flow/React/Redux that many people could not wrap their heads around (including myself). I hope to grok Flow as deeply as you some day. 😅

Contributor

AndrewSouthpaw commented Mar 5, 2018

Merged. I think we've explored this PR long enough. @villesau, great work, really. You've tackled a critical and buggy part of Flow/React/Redux that many people could not wrap their heads around (including myself). I hope to grok Flow as deeply as you some day. 😅

echenley added a commit to echenley/coinography that referenced this pull request Mar 6, 2018

echenley added a commit to echenley/coinography that referenced this pull request Mar 6, 2018

echenley added a commit to echenley/coinography that referenced this pull request Mar 6, 2018

mcous added a commit to Opentrons/opentrons that referenced this pull request Mar 6, 2018

ci(flow): Upgrade flow, lock flow-typed defs to v0.61, re-enable flow
The newer definition for react-redux introduced in flow-typed/flow-typed#1731 blows up on many of our
containers. Locking flow-typed to v0.61 defs gets us the old definition, which our codebase passes
on. A reading of both the old and the new definitions makes me question how things were ever
working, but for now this should make flow usable again.

meriadec added a commit to meriadec/ledger-live-desktop that referenced this pull request Mar 7, 2018

@Ashoat

This comment has been minimized.

Show comment
Hide comment
@Ashoat

Ashoat Mar 8, 2018

Contributor

As @AndrewSouthpaw suggested I opened an issue regarding the removal of the type parameterization in the return of MapStateToProps: #1934

@villesau, would really appreciate if you could just elaborate on your explanation in that issue. Totally buy that it's a Flow issue, just curious what the issue is specifically, and having an issue to track it won't hurt.

Contributor

Ashoat commented Mar 8, 2018

As @AndrewSouthpaw suggested I opened an issue regarding the removal of the type parameterization in the return of MapStateToProps: #1934

@villesau, would really appreciate if you could just elaborate on your explanation in that issue. Totally buy that it's a Flow issue, just curious what the issue is specifically, and having an issue to track it won't hurt.

@williamboman

This comment has been minimized.

Show comment
Hide comment
@williamboman

williamboman Mar 9, 2018

This is so awesome, thanks!!

I'm having issues getting this to work using the class decorator syntax. Is it known whether using decorators is supported or not? Currently working around this by:

class _Foo extends PureComponent<Props> {...}
export default connect(mapStateToProps)(_Foo)

williamboman commented Mar 9, 2018

This is so awesome, thanks!!

I'm having issues getting this to work using the class decorator syntax. Is it known whether using decorators is supported or not? Currently working around this by:

class _Foo extends PureComponent<Props> {...}
export default connect(mapStateToProps)(_Foo)
@AndrewSouthpaw

This comment has been minimized.

Show comment
Hide comment
@AndrewSouthpaw

AndrewSouthpaw Mar 12, 2018

Contributor

That's what my team does as well. I don't think class decorator syntax is supported, though I never tried. That'd be a question for the flow team.

Contributor

AndrewSouthpaw commented Mar 12, 2018

That's what my team does as well. I don't think class decorator syntax is supported, though I never tried. That'd be a question for the flow team.

@marcelmokos

This comment has been minimized.

Show comment
Hide comment
@marcelmokos

marcelmokos Mar 12, 2018

Contributor

We were using Connector in our project. If the component is not directly in a function call will it type, how should we adjust the types?

const withSearchFormConnect: Connector<*, SearchFormProps> = connect(
 // mapStateToProps
  (state, ownProps) => ({
      ...ownProps,
     search: state.search,
  }),
  // mapDispatchToProps
  {
      setLocation: location => duck.actionCreator.setLocation({location}),
  }
);


const SearchForm = compose(withSearchFormConnect, withSomethigElse)(
  SearchFormPresenter,
);
Contributor

marcelmokos commented Mar 12, 2018

We were using Connector in our project. If the component is not directly in a function call will it type, how should we adjust the types?

const withSearchFormConnect: Connector<*, SearchFormProps> = connect(
 // mapStateToProps
  (state, ownProps) => ({
      ...ownProps,
     search: state.search,
  }),
  // mapDispatchToProps
  {
      setLocation: location => duck.actionCreator.setLocation({location}),
  }
);


const SearchForm = compose(withSearchFormConnect, withSomethigElse)(
  SearchFormPresenter,
);
@bgergen

This comment has been minimized.

Show comment
Hide comment
@bgergen

bgergen Mar 12, 2018

I am having a similar issue as @marcelmokos. We currently use Connector as well. I would like to update, but not sure what adjustments to make after removing Connector.

@villesau @AndrewSouthpaw Is there a recommended migration path to using the new libdef?

bgergen commented Mar 12, 2018

I am having a similar issue as @marcelmokos. We currently use Connector as well. I would like to update, but not sure what adjustments to make after removing Connector.

@villesau @AndrewSouthpaw Is there a recommended migration path to using the new libdef?

@AndrewSouthpaw

This comment has been minimized.

Show comment
Hide comment
@AndrewSouthpaw

AndrewSouthpaw Mar 13, 2018

Contributor

You don't need to add anything once you've removed Connector, you can look at the tests for an example. If it's not making sense, LMK, and I can post an example.

For migration, I did a few regex find/replaces with my IDE, it wasn't too painful, took maybe 15-30 minutes. Find/replace hit 95% of cases, then a few with custom syntax I cleaned up manually.

Contributor

AndrewSouthpaw commented Mar 13, 2018

You don't need to add anything once you've removed Connector, you can look at the tests for an example. If it's not making sense, LMK, and I can post an example.

For migration, I did a few regex find/replaces with my IDE, it wasn't too painful, took maybe 15-30 minutes. Find/replace hit 95% of cases, then a few with custom syntax I cleaned up manually.

@Ashoat

This comment has been minimized.

Show comment
Hide comment
@Ashoat

Ashoat Mar 13, 2018

Contributor

Try just using the inferred type (*) instead of Connector

Contributor

Ashoat commented Mar 13, 2018

Try just using the inferred type (*) instead of Connector

@bgergen

This comment has been minimized.

Show comment
Hide comment
@bgergen

bgergen Mar 14, 2018

@AndrewSouthpaw Thanks, I looked through the tests and adjusted my code accordingly. It seems that I lose typechecking in the passing of props into the component being wrapped by connect. Previously, I could type my connect function as Connector<OwnProps, WrappedComponentProps>, with WrappedComponentProps having been imported from the wrapped component's file. With this configuration, if I tried to return an incorrect type from my mergeProps function, flow would catch it and give me an error. Now, without the Connector type, and without WrappedComponentProps explicitly imported, flow no longer catches these errors.

The tests make it seem like flow should be catching these errors. Got any ideas? I would say that I could still import WrappedComponentProps and use that as the return type from mergeProps, but that leaves me in a strange spot in components that don't use a mergeProps function. Is this possibly an issue that flow has with HOCs in general?

Edit: Using WrappedComponentProps as the return type from mergeProps doesn't seem to work consistently either.

bgergen commented Mar 14, 2018

@AndrewSouthpaw Thanks, I looked through the tests and adjusted my code accordingly. It seems that I lose typechecking in the passing of props into the component being wrapped by connect. Previously, I could type my connect function as Connector<OwnProps, WrappedComponentProps>, with WrappedComponentProps having been imported from the wrapped component's file. With this configuration, if I tried to return an incorrect type from my mergeProps function, flow would catch it and give me an error. Now, without the Connector type, and without WrappedComponentProps explicitly imported, flow no longer catches these errors.

The tests make it seem like flow should be catching these errors. Got any ideas? I would say that I could still import WrappedComponentProps and use that as the return type from mergeProps, but that leaves me in a strange spot in components that don't use a mergeProps function. Is this possibly an issue that flow has with HOCs in general?

Edit: Using WrappedComponentProps as the return type from mergeProps doesn't seem to work consistently either.

@ryami333

This comment has been minimized.

Show comment
Hide comment
@ryami333

ryami333 Mar 14, 2018

@bgergen are you using a recent release of Flow? I know that perhaps 3-4 months ago there was a bit of an overhaul to error handling and it fixed a bunch of errors being located in the wrong place (or not at all).

ryami333 commented Mar 14, 2018

@bgergen are you using a recent release of Flow? I know that perhaps 3-4 months ago there was a bit of an overhaul to error handling and it fixed a bunch of errors being located in the wrong place (or not at all).

@bgergen

This comment has been minimized.

Show comment
Hide comment
@bgergen

bgergen Mar 14, 2018

@ryami333 I'm on the newest version I believe: 0.67.1

I ran into a different issue. Props were being checked, but defaultProps in my wrapped components were causing all sorts of conflicts with the ElementConfig type in the libdef. Changing ElementConfig to ElementProps in the libdef seems to have fixed the issue.

@AndrewSouthpaw @villesau Does any of that make sense?

bgergen commented Mar 14, 2018

@ryami333 I'm on the newest version I believe: 0.67.1

I ran into a different issue. Props were being checked, but defaultProps in my wrapped components were causing all sorts of conflicts with the ElementConfig type in the libdef. Changing ElementConfig to ElementProps in the libdef seems to have fixed the issue.

@AndrewSouthpaw @villesau Does any of that make sense?

@villesau

This comment has been minimized.

Show comment
Hide comment
@villesau

villesau Mar 14, 2018

Contributor

@bgergen Can you provide an example code where libdefs fails? Without example it's not possible to try to figure out what is wrong.

Contributor

villesau commented Mar 14, 2018

@bgergen Can you provide an example code where libdefs fails? Without example it's not possible to try to figure out what is wrong.

@stryju

This comment has been minimized.

Show comment
Hide comment
@stryju

stryju Mar 21, 2018

Seems that this introduces regressions, like lack of getWrappedInstance() for connect-ed component.

Also, mapStateTo* is missing factory annotation:
edit: Ok, this seems to work if annotated properly :)

function mapStateToProps(initialState, initialOwnProps) => {
  // some per component instance optimization
  return (state, ownProps) => ({
    /* ... */
  });
}

stryju commented Mar 21, 2018

Seems that this introduces regressions, like lack of getWrappedInstance() for connect-ed component.

Also, mapStateTo* is missing factory annotation:
edit: Ok, this seems to work if annotated properly :)

function mapStateToProps(initialState, initialOwnProps) => {
  // some per component instance optimization
  return (state, ownProps) => ({
    /* ... */
  });
}
@mike1808

This comment has been minimized.

Show comment
Hide comment
@mike1808

mike1808 Mar 21, 2018

Contributor

please add support for factory mapStateToProps

Contributor

mike1808 commented Mar 21, 2018

please add support for factory mapStateToProps

@goodmind

This comment has been minimized.

Show comment
Hide comment
@goodmind

goodmind Mar 25, 2018

It infers empty even for class components. What am I missing?
Can't do $Diff on React.ElementConfig

goodmind commented Mar 25, 2018

It infers empty even for class components. What am I missing?
Can't do $Diff on React.ElementConfig

Ashoat added a commit to Ashoat/flow-typed that referenced this pull request Apr 2, 2018

[react-redux] Remove export keyword from declarations
Avoiding any `export` declarations means that all declared modules are available as properties on the default export. The version of this libdef before the [revamp](flow-typed#1731) didn't have any `export` keywords, presumably for that reason.

Why not use named imports, you say? I have a specific case where I can't use named imports: the new module support in Node.js 9.3 can't handle named imports on commonjs modules. And since I'm not able to use the ESM version of `react-redux` because it relies on a named import from `react` (which doesn't have an ESM version), I have to use the commonjs version.

Regardless: as `react-redux` does indeed have a default export, the libdef should be declared accordingly.
declare export function connect<Com: ComponentType<*>, SP: Object, RSP: Object, MDP: Object>(
mapStateToProps: MapStateToProps<SP, RSP>,
mapDispatchToPRops: MDP
): (component: Com) => ComponentType<$Diff<$Diff<ElementConfig<Com>, RSP>, MDP> & SP>;

This comment has been minimized.

@Hypnosphi

Hypnosphi Apr 6, 2018

Contributor

Here, the component is expected to receive MDP as part of props shape. This assumes that the dispatch signature is A => A which is not true if you use almost any middleware (e.g. redux-thunk)

@Hypnosphi

Hypnosphi Apr 6, 2018

Contributor

Here, the component is expected to receive MDP as part of props shape. This assumes that the dispatch signature is A => A which is not true if you use almost any middleware (e.g. redux-thunk)

This comment has been minimized.

@stryju

stryju Apr 18, 2018

same goes for

declare export class Provider<S, A> extends React$Component<{
  store: Store<S, A>,
  children?: any
}> {}

should probably be

declare export class Provider<S, A, D = Dispatch<A>> extends React$Component<{
  store: Store<S, A, D>,
  children?: any
}> {}
@stryju

stryju Apr 18, 2018

same goes for

declare export class Provider<S, A> extends React$Component<{
  store: Store<S, A>,
  children?: any
}> {}

should probably be

declare export class Provider<S, A, D = Dispatch<A>> extends React$Component<{
  store: Store<S, A, D>,
  children?: any
}> {}

AndrewSouthpaw added a commit that referenced this pull request Apr 9, 2018

[react-redux] Add default export (#2018)
* [react-redux] Remove export keyword from declarations

Avoiding any `export` declarations means that all declared modules are available as properties on the default export. The version of this libdef before the [revamp](#1731) didn't have any `export` keywords, presumably for that reason.

Why not use named imports, you say? I have a specific case where I can't use named imports: the new module support in Node.js 9.3 can't handle named imports on commonjs modules. And since I'm not able to use the ESM version of `react-redux` because it relies on a named import from `react` (which doesn't have an ESM version), I have to use the commonjs version.

Regardless: as `react-redux` does indeed have a default export, the libdef should be declared accordingly.

* Explicitly list default exports
@abeforgit

This comment has been minimized.

Show comment
Hide comment
@abeforgit

abeforgit Jun 9, 2018

I'm not sure if this is the appropriate place for it, but I'm not sure where else to mention this, it might save someone else the 5 hours I just lost.

While I haven't had any issues in getting type checking to work, the connect function provided here broke intellisense on every editor I've tried (webstorm, atom+nuclide, vscode). The solution I've found is to simply cast the connect call to the appropriate ComponentType, like so:

import type {ComponentType} from 'react'
import type {Props} from "./MyComponent"
import {MyComponent} from "./MyComponent"

// ...

export default (connect(mapStateToProps, mapDispatchToProps)(MyComponent): ComponentType<Props>)

Note it has to be ComponentType imported as above, React.Component imported from import * as React from 'react' will not work.

Edit: just realised, ComponentType is still available when using import * as React from 'react' just use as React.ComponentType

abeforgit commented Jun 9, 2018

I'm not sure if this is the appropriate place for it, but I'm not sure where else to mention this, it might save someone else the 5 hours I just lost.

While I haven't had any issues in getting type checking to work, the connect function provided here broke intellisense on every editor I've tried (webstorm, atom+nuclide, vscode). The solution I've found is to simply cast the connect call to the appropriate ComponentType, like so:

import type {ComponentType} from 'react'
import type {Props} from "./MyComponent"
import {MyComponent} from "./MyComponent"

// ...

export default (connect(mapStateToProps, mapDispatchToProps)(MyComponent): ComponentType<Props>)

Note it has to be ComponentType imported as above, React.Component imported from import * as React from 'react' will not work.

Edit: just realised, ComponentType is still available when using import * as React from 'react' just use as React.ComponentType

@stryju

This comment has been minimized.

Show comment
Hide comment
@stryju

stryju Jun 10, 2018

@abeforgit I think you'll want to have a $Diff of your comopnent's config and store props, like:

type ConnectedComponent = ComponentType<$Diff<ElementConfig<ComponentType<Props>>, StoreProps>>;
export default (connect(mapStateToProps, mapDispatchToProps)(MyComponent): ConnectedComponent);

stryju commented Jun 10, 2018

@abeforgit I think you'll want to have a $Diff of your comopnent's config and store props, like:

type ConnectedComponent = ComponentType<$Diff<ElementConfig<ComponentType<Props>>, StoreProps>>;
export default (connect(mapStateToProps, mapDispatchToProps)(MyComponent): ConnectedComponent);
@abeforgit

This comment has been minimized.

Show comment
Hide comment
@abeforgit

abeforgit Jun 11, 2018

@stryju Strangely enough, while your suggestion does provide intellisense, it stops the flow typechecking (as in it does not error when props are missing). However the below does work:

type connectedComponent = React.ComponentType<$Diff<React.ElementConfig<typeof MyComponent>, StoreProps>>

Which is weird since the type of the component should be ComponentType<Props>... which is the same as your suggestion...

Ill use this for now as it is better than my original solution, but it still feels like there should be a better way

abeforgit commented Jun 11, 2018

@stryju Strangely enough, while your suggestion does provide intellisense, it stops the flow typechecking (as in it does not error when props are missing). However the below does work:

type connectedComponent = React.ComponentType<$Diff<React.ElementConfig<typeof MyComponent>, StoreProps>>

Which is weird since the type of the component should be ComponentType<Props>... which is the same as your suggestion...

Ill use this for now as it is better than my original solution, but it still feels like there should be a better way

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment