Aaron Aaron - 1 month ago 34
React JSX Question

TypeScript workaround for rest props in React

TypeScript supports JSX spread attributes which is commonly used in React to pass HTML attributes from a component to a rendered HTML element:

interface LinkProps extends React.HTMLAttributes {
textToDisplay: string;
}

class Link extends React.Component<LinkProps, {}> {
public render():JSX.Element {
return (
<a {...this.props}>{this.props.textToDisplay}</a>
);
}
}

<Link textToDisplay="Search" href="http://google.com" />


However, React introduced a warning if you pass any unknown props to an HTML element. The above example would produce a React runtime warning that
textToDisplay
is an unknown prop of
<a>
. The suggested solution for a case like this example is to use object rest properties to extract out your custom props and use the rest for the JSX spread attributes:

const {textToDisplay, ...htmlProps} = this.props;
return (
<a {...htmlProps}>{textToDisplay}</a>
);


But TypeScript does not yet support this syntax. I know that hopefully some day we will be able to do this in TypeScript. In the meantime what are some workarounds? I'm looking for a solution that doesn't compromise type-safety and finding it surprisingly difficult. For example I could do this:

const customProps = ["textDoDisplay", "otherCustomProp", "etc"];
const htmlProps:HTMLAttributes = Object.assign({}, this.props);
customProps.forEach(prop => delete htmlProps[prop]);


But this requires the use of string property names that are not validated against the actual props and thus prone to typos and bad IDE support. Is there a better way we can do this?

Answer

You probably can't avoid creating a new object with a subset of the properties of this.props, but you can do that with type safety.

For example:

interface LinkProps {
    textToDisplay: string;
}

const LinkPropsKeys: LinkProps = { textToDisplay: "" };

class Link extends React.Component<LinkProps & React.HTMLAttributes, {}> {
    public render(): JSX.Element {
        return (
            <a { ...this.getHtmlProps() }>{ this.props.textToDisplay }</a>
        );
    }

    private getHtmlProps(): React.HTMLAttributes {
        let htmlProps = {} as React.HTMLAttributes;

        for (let key in this.props) {
            if (!(LinkPropsKeys as any)[key]) {
                htmlProps[key] = this.props[key];
            }
        }

        return htmlProps;
    }
}

Using LinkPropsKeys object, which needs to match the LinkProps, will help you keep the keys between the interface and the runtime lookup synchronized.