Search code examples
reactjsflowtyperecompose

How to get flow to typecheck props given to it from a HOC?


For example:

import { withProps } from 'recompose'

type Props = {
  name: string,
};

const NameTag = ({ name }: Props) => (
  <p style={{color: 'pink'}}>
    { name }
  </p>
);

const MyNameTag = withProps({
  // i want a error here
  name: 1
})(NameTag);

How can I get an error that I cannot pass the number 1 to the prop name for the NameTag component?

Thanks.


Solution

  • At least Flow 0.78 will give you an error as soon as you use the HOC.

    So if you add a <MyNameTag /> on your code, it should give you this.

    string [1] is incompatible with number [2] in property name of the first argument.
    
     [1]  7│   name: string,
          8│ };
          9│
         10│ const NameTag = ({ name }: Props) => (
         11│   <p style={{color: 'pink'}}>
         12│     { name }
         13│   </p>
           :
     [2] 19│   name: 1
    

    This probably happens because you can still pass name when instancing NameTag and up until that point it's not clear that it will fail.

    Flow is so complex and my flow-fu is weak so I would not dare to say this is a flow bug.

    So far, the only way I have found to catch errors on declarations is to build a HOC that only allows the properties that were not overridden and nothing else. You will need to declare components with strict properties {| a: string |}. Check this example:

    // @flow
    
    import * as React from 'react'
    
    
    function myWithProps<
        MP, // My Properties. These are the pre-entered properties.
        CP, // Component properties. The properties originally expected by
            // the component.
        C: React.ComponentType<CP>, // The original component itself. Used to
                                    // extract the properties template CP.
        IP: $Diff<CP, MP>, // Input properties. Properties that must be received
                           // by the HOC. We only expect the properties of the
                           // original component CP minus the properties MP that
                           // were specified when we created the HOC.
        >(myProps: MP): C => React.ComponentType<IP> {
            return component => props => <component {...myProps} {...props} />;
    }
    
    
    type Props = {|
      name: string,
      age: number,
    |};
    
    
    const NameTag = ({ name, age }: Props) => (
      <p style={{color: 'pink'}}>
        { name } { age }
      </p>
    );
    
    
    // $ExpectError. name has a wrong type.
    const MyNameTag = myWithProps({name: 1})(NameTag);
    
    // Should work.
    const MyOtherNameTag = myWithProps({name: "hello"})(NameTag);
    
    // Should work.
    <MyOtherNameTag age={21} />;
    
    // $ExpectError. name cannot be overriden.
    <MyOtherNameTag age={21} name={"John"} />;
    
    // $ExpectError. age has a wrong type.
    <MyOtherNameTag age={"21"} />;
    
    // $ExpectError. age missing.
    <MyOtherNameTag />;
    
    // $ExpectError. Unexpected parameter.
    <MyOtherNameTag age={21} nmae={"John"} />;
    

    myWithProps uses generic types so it should work with any class.

    I hope this helps!