I have two components -- one being a child and the other a parent. The parent component is designed to be a HOC that makes an ajax call for data, and then it passes that data down to its child. The props that it passes down to the children is dynamically added through React.CloneElement with the this.props.children.

With regards to flow, it isn't able to interpret/see the additional props I've added when re-cloning.

How do I get past this?

How both components relate.

<ParentComponent>
     </ChildComponent />
</ParentComponent>
const ParentComponent = (props) => {
     // code
     return (
          <>
               {React.cloneElement(this.props.children, { extraProps }}
          <>
      );
}

type PropTypes = {
     extraProps: string, // flow complains, and I don't know how to get it to recognize that the extraProps is added dynamically.
};

const ChildComponent = (props) => {
    // code that uses the additional `extraProps`
}

2 Answers

0
AndrewSouthpaw On

So far I haven't found a good way to get it to work completely. Here's some code that will set it in the right direction though.

import * as React from 'react'

const ParentComponent = (props: { children: React.Element<typeof ChildComponent> }) => {
     return (React.cloneElement(props.children, { extraProps: 'hello' }));
     // return (React.cloneElement(props.children, { extraProps: 1 }));  // <-- this will error
}

type PropTypes = {
     extraProps?: string, // not great because you'll still have to check presence, but at least it provides
                          // type checking on extraProps in the ParentComponent
};

const ChildComponent = (props: PropTypes) => {
  return <p>{props.extraProps}</p>
}

<ParentComponent><ChildComponent /></ParentComponent>

Flow Try

0
Community On

Flow has some great documentation on typing higher-order components. It changes your implementation slightly, but I think it should achieve the same effect as your code. Using the documentation's example for injecting props, you could write,

import * as React from 'react';

type Props = {
  x: string,
  y: string,
}

function injectY<Config: {}>(
  Component: React.AbstractComponent<Config>
): React.AbstractComponent<$Diff<Config, { y: string | void }>> {
  return function WrapperComponent(
    props: $Diff<Config, { y: string | void }>
  ) {
    return <Component {...props} y="injected" />;
  }
}

function Child({ x, y }: Props) {
  return x + y;
}

const EnhancedChild = injectY(Child);

function App() {
  return (
    <div>
      <Child x="explicit" y="explicit" />
      <EnhancedChild x="explicit" />
    </div>
  );
}

Try Flow

The injectY prop is generic right now so you can reuse that pattern for other places where you might be using React.cloneElement. I'm not familiar with React.cloneElement so I can't say much about the differences between the cloning and HOC approaches other than you'll probably get better type checking with injectY.