Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typing a React component that clones its children to add extra props in TypeScript

Assuming you have following component that accepts one or more child JSX.Elements and passes extra callback to their props when they are rendered by using React.cloneElement(child, { onCancel: () => {} }.

Component in question (excerpt):

interface XComponentProps {
    onCancel: (index: number) => void;
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        const children = typeof this.props.children === 'function' ?
            React.cloneElement(this.props.children, { onCancel: () => onCancel() }) :
            this.props.children.map((child, idx) => (
                React.cloneElement(child, { onCancel: () => onCancel(idx) })
            ));
        return <div>{children}</div>;
    }
}

Component in question in action (excerpt):

interface UserComponentProps { caption: string }
const UserComponent = (props: UserComponentProps) => (
    <button onClick={props.onClose}>{props.caption || "close"}</button>
);

ReactDOM.render(
    <XComponent onCancel={handleCancel}>
        <UserComponent />
        <UserComponent />
        <UserComponent />
    </XComponent>
);

Right now TSC is complaining that UserComponent does not have onCancel in its props' interface definition, which indeed does not. One easiest fix is to manually define onCancel to UserComponentProps interface.

However, I want to fix it without modifying child node's prop definition so that the component can accept arbitrary set of React elements. In such scenario, is there a way to define typings of returning elements that has extra implicit props passed at the XComponent (parent) level?

like image 551
shuntksh Avatar asked Jan 26 '17 19:01

shuntksh


1 Answers

It is not possible. There is no way to know statically what props UserComponent receives from parent XComponent in your ReactDOM.render context.

If you want a type safe solution, use children as functions:

Here is XComponent definition

interface XComponentProps {
    onCancel: (index: number) => void;
    childrenAsFunction: (props: { onCancel: (index: number) => void }) => JSX.Element;
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        return <div>{childrenAsFunction({ onCancel })}</div>;
    }
}

Now you can use it to render your UserComponents

<XComponent onCancel={handleCancel} childrenAsFunction={props => 
  <span>
    <UserComponent {...props} />
    <UserComponent {...props} />
  </span>
} />

This will work nicely, I use this pattern often with no hassle. You can refactor XComponentProps to type the childrenAsFunction prop with the relevant part (the onCancel function here).

like image 62
Benoit B. Avatar answered Oct 07 '22 08:10

Benoit B.