This example give a general idea of the problem:
const err = <HOCWrappedClass />; // Class component wrapped in HOC type checks (expect prop `name`)
const ok = <HOCWrappedClass name='Sally' />;
const err1 = <HOCWrappedStateless />; // This should error! Stateless component wrapped in HOC doesn't type check
const ok1 = <HOCWrappedStateless name='Sally' />;
const err2 = <SimpleStateless />; // Simple stateless component type checks (expect prop `name`)
const ok2 = <SimpleStateless name='Sally' date={new Date()} />;
@minedeljkovic created a repo reproducing this on https://github.com/minedeljkovic/flow-hoc-export-problem for PR https://github.com/facebook/flow/pull/1821#issuecomment-233539595 .
In the same PR @gcanti provided a workaround, but it's not applicable to all cases.
To be more specific, I'm trying to create a Relay.createContainer that preserve the base component type info and add a few others type checks (Code: https://gist.github.com/rodrigopr/c0111e9dacdb2351854ea8b420676cb8).
It's working fine for Class components, but stateless functions lose props checking when wrapped in the provided createRelayContainer.
Let me know if there is anything I can do to help fix this.
I got the following error
src/relay.js:29
29: declare class RelayContainer<D, P, F, V> extends React$Component<D, P, void> {
^ undefined. Did you forget to declare some incompatible instantiation of `D`?. This type is incompatible with
29: declare class RelayContainer<D, P, F, V> extends React$Component<D, P, void> {
^ some incompatible instantiation of `D`
src/relay.js:29
29: declare class RelayContainer<D, P, F, V> extends React$Component<D, P, void> {
^ D. This type is incompatible with
29: declare class RelayContainer<D, P, F, V> extends React$Component<D, P, void> {
^ undefined. Did you forget to declare D?
Also could you please provide some examples of usage of createRelayContainer with both Class components and stateless functions which I can use as tests?
Some observations after reading the code
createContainerDef owns 5 type parameters but is used with 0 export const createRelayContainer: createContainerDef = Relay.createContainer
type PropsWithoutRelay<P> = $Diff<{ relay: RelayInstance<*> }, P>; given the name I guess should be the other way around type PropsWithoutRelay<P> = $Diff<P, { relay: RelayInstance<*> }>;, the signature is $Diff<Props, DefaultProps>
in the createContainerDef definition, StatelessComponent takes D as type parameter, should be P
in general default props and $Diff don't play well with functional components, try to remove them (if possible) or define 2 overloadings
Thanks for the quick answer @gcanti, indeed there was few bugs with in code of the gist.
I've fixed those you mentioned locally, but the problem persists.
The problem only happens when importing the wrapped SFC in another module.
I think we can reduce the problem to this case:
type StatelessComponent<P> = (props: P) => ?React$Element<any>;
export function someHOC<P>(
BaseComponent: StatelessComponent<P>
): StatelessComponent<P> {
return BaseComponent;
}
type Props = { name: string };
export const Example = (props: Props) => <span>{props.name}</span>;
export const WrappedExample = someHOC(Example);
// uncomment the following line and the check start working in the other file too
// const work = <WrappedExample name={123} />;
And in another file
import { Example, WrappedExample } from './index';
const works = <Example name={123} />;
const dontWork = <WrappedExample name={123} />; // it start working if we uncomment the line in the other file
Now I got
13: export const WrappedExample = someHOC(Example);
^^^^^^^^^^^^^^^^ type parameter `P` of function call. Missing annotation
Fixing with
export const WrappedExample: StatelessComponent<Props> = someHOC(Example);
makes it work in the other file too
@gcanti, seems like a type inference bug.
I wasn't seeing this because I was running slightly different, in 3 files instead of 2.
Interesting that the type parameterPof function call goes away if we do that, and we get the behavior that I described on exported WrappedExample .
The two scenarios might be related and the type inference error might be the root of the bug.
// file hoc.js
type StatelessComponent<P> = (props: P) => ?React$Element<any>;
export function someHOC<P>(
BaseComponent: StatelessComponent<P>
): StatelessComponent<P> {
return BaseComponent;
}
// file container.js
import { someHOC } from './hoc';
type Props = { name: string };
export const Example = (props: Props) => <span>{props.name}</span>;
export const WrappedExample = someHOC(Example);
// uncomment the following line and the check start working in the other file too
// const work = <WrappedExample name={123} />;
// file test.js
import { Example, WrappedExample } from './container';
const works = <Example name={123} />;
const dontWork = <WrappedExample name={123} />; // it start working if we uncomment the line in the other file
The workaround (export const WrappedExample: StatelessComponent<Props> =) won't work for my use case because I'm relying on type inference to avoid really big type signatures.
I've run the last scenario with traces option. (output: https://gist.github.com/rodrigopr/60477b637c39fa9842ec0e602ecc22f0)
On line 17 (ObjT [props of React element 'WrappedExample']) is tracing back to the other usage (the one that is commented in the previous example).
So I guess there is some caching that make it work in some scenarios (same file/scope), but fail when cache is not primed.
Probably something around here: https://github.com/facebook/flow/blob/master/src/typing/statement.ml#L3445-L3513
I'm especially suspecting of (Env.get_var) in:
let c = Env.get_var cx name reason in
let o = mk_props reason c in
but i'm still learning how to debug those values locally in ocaml.
Debugging the values [1] in src/typing/statement.ml#L3445-L3513 didn't give me any insight on the problem.
I'd appreciate if someone familiar with flow internals takes a look at this, to at least provide a general idea of what might be wrong and how to processed.
[1] for instance, values of Env.get_var cx name reason: https://gist.github.com/rodrigopr/7ec3e16e6ad0b2ab322ac45488cf985d
Just to point out for anyone experiencing this, few workarounds to this issue are described in:
https://github.com/saltycrane/flowtype-hoc-exploration/blob/notes/README.md
and
https://github.com/pcardune/flowtype-hoc-exploration/issues/2
We鈥檒l be providing more guidance on typing HOCs in an upcoming version of Flow 馃憤
@calebmer
Are there will be any changes in the amount of genetic parameters in React.Component?
TypeScript has two.
Flow still has this unnecessary third parameter for default params. This unnecessarily hinders the proper description of the higher order component.
We will only have two in an upcoming release and only one if you don't have state.
Awesome @calebmer, type-safe HOCs has been the main point of problems with flow to us, looking forward to test the next versions. :)
And again, no errors appeared
// @flow
import * as React from 'react';
type Props = {
demo: string,
}
export default (props: Props): React.Node => <div>{props.demo}</div>;
and then
<ProductDemo />
no errors :( should be demo prop required
:key: version 0.63.1
Most helpful comment
We will only have two in an upcoming release and only one if you don't have state.