I've added typoes to a handful of HOCs using the recommended AbstractComponent types as per flow.org and each one seems to work well by itself. I recently ran into trouble when I tried to chain a couple HOCs together.
Here's the simplest example (also on flow.org/try):
// @flow
import React, { type AbstractComponent } from 'react';
export function withA<OwnProps: { a: number }>(
Subject: AbstractComponent<OwnProps>,
): AbstractComponent<$Diff<OwnProps, { a: number }>> {
return function(props: $Diff<OwnProps, { a: number }>) {
return <Subject {...props} a={1} />;
}
}
export function withB<OwnProps: { b: number }>(
Subject: AbstractComponent<OwnProps>,
): AbstractComponent<$Diff<OwnProps, { b: number }>> {
return function(props: $Diff<OwnProps, { b: number }>) {
return <Subject {...props} b={2} />;
}
}
type Props = {| a: number, b: number, c: number |};
function Numbers({ a, b, c }: Props) {
return <div>{`${a} ${b} ${c}`}</div>;
}
const NumbersWithA = withA(Numbers);
const NumbersWithAandB = withB(NumbersWithA);
// This last line blows up on the call to withB.
function Example() {
return <NumbersWithAandB c={3} />;
}
Here's the error:

Am I doing something wrong? Do you have any advice on how to type my HOCs differently?
Thanks!
Not sure exactly the cause of the error here, @jbrown215 may know when he's back from time off. However, I think you can fix this error by adding an annotation on NumbersWithA like so.
@mvitousek Yeah, you're absolutely right that the types can be coerced with hints like you suggested. Unfortunately, I'm trying to chain HOCs using Redux's compose, and it breaks the same way there without the opportunity to coerce. Even without compose, this requires the average feature developer to have a lot of internal knowledge of the HOC and advanced flow utils, so I'd love if I (the HOC dev) could make it simpler.
Maybe related: It looks like all type safety is broken when using the HOC types suggested by the docs.
Update: looks like this example properly produces errors on 0.98.1 but not on 0.99. I'll create a separate issue for this.
Using spread instead of Diff seems to work better.
There are a lot of known issues around polymorphism + type destructors, so I'm going to rewrite the docs to use spread to inject props instead.
@jbrown215 This solution unfortunately doesn't keep working quite as smoothly when you want to export the resulting component from the module, which at least in my experience is the typical case. Adding export like so:
export const NumbersWithA = withA(Numbers);
export const NumbersWithAandB = withB(NumbersWithA);
produces errors saying "Missing type annotation for OwnProps." So at each call site, the user of the HOC would need to explicitly define their OwnProps type and pass it as a type argument to the HOC.
I believe the root of the issue is that in this HOC type definition:
type InjectedPropsA = {| a: number |};
declare function withA<OwnProps>(
Subject: AbstractComponent<{| ...InjectedPropsA, ...OwnProps |}>,
): AbstractComponent<OwnProps>;
the OwnProps type is left floating, with nothing in the arguments totally pinning it down. Concretely, nothing constrains whether it itself has the a property or not.
Another symptom of this is that if you don't try to export the resulting component, so that Flow is content to infer the OwnProps type variable just from the component's use within the module, the resulting component can end up accepting the props that the HOCs are meant to provide:
function Example() {
return <NumbersWithAandB c={3} a={1} />; // spurious 'a' prop
}
Type destructors like $Diff can be great for this kind of task (when they work correctly) because they can express how to compute the result type forward from the argument types, rather than leave it floating as a type variable. I know there's a lot of work happening on important other aspects of Flow, so I'll just say that fixes to these issues with type destructors are another area I think would be quite excellent :-)
I know there's a lot of work happening on important other aspects of Flow
This work is actually one of our top priorities right now, but it's blocked. @dsainati1 and I are actively working on unblocking this project!
This work is actually one of our top priorities right now, but it's blocked. @dsainati1 and I are actively working on unblocking this project!
Great to hear! Looking forward to it.
Most helpful comment
This work is actually one of our top priorities right now, but it's blocked. @dsainati1 and I are actively working on unblocking this project!