TypeScript workaround for props rest in reagent

Updated for TypeScript 2.1

TypeScript 2.1 now supports distribution / rest of objects , so workarounds are no longer required!


Original question

TypeScript supports JSX propagation attributes that are 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 has issued a warning if you pass any unknown property to an HTML element . The above example will textToDisplay a React runtime warning that textToDisplay is an unknown support <a> . A suggested solution for a case like this is to use the rest properties of the object to retrieve your user details and use the rest for the JSX propagation attributes:

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

But TypeScript does not yet support this syntax. I know that I hope someday we can do this in TypeScript . ( Update : TS 2.1 now supports distribution / rest of objects ! Why are you still reading this?) In the meantime, what are some workarounds? I am looking for a solution that does not jeopardize type safety, and find 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 for this it is necessary to use the names of string properties that are not checked for compliance with the actual details and therefore are subject to typos and poor support for the IDE. Is there a better way to do this?

+17
source share
5 answers

You probably won't be able to create a new object with a subset of this.props properties, but you can do it with type safety.

For instance:

 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 the LinkPropsKeys object, which must match LinkProps , will help you synchronize keys between the interface and runtime synchronization.

+5
source

The React.HtmlAttributes in the above example is now generic, so I needed to go from React.AnchorHTMLAttributes<HTMLAnchorElement> .

Example:

 import React from 'react'; type AClickEvent = React.MouseEvent<HTMLAnchorElement>; interface LinkPropTypes extends React.AnchorHTMLAttributes<HTMLAnchorElement> { to: string; onClick?: (x: AClickEvent) => void; } class Link extends React.Component<LinkPropTypes> { public static defaultProps: LinkPropTypes = { to: '', onClick: null, }; private handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { ... event.preventDefault(); history.push(this.props.to); }; public render() { const { to, children, ...props } = this.props; return ( <a href={to} {...props} onClick={this.handleClick}> {children} </a> ); } } export default Link; 
+2
source

I accepted Nitzen Tomer because it was the main idea that I was going for.

As a more generalized solution, this is what I came across:

 export function rest(object: any, remove: {[key: string]: any}) { let rest = Object.assign({}, object); Object.keys(remove).forEach(key => delete rest[key]); return rest; } 

Therefore, I can use it as follows:

 const {a, b, c} = props; const htmlProps = rest(props, {a, b, c}); 

And as soon as TypeScript supports the rest / spread object, I can just search for all rest() applications and simplify it to const {a, b, c, ...htmlProps} = props .

+1
source

Such a getter might work:

 class Link extends React.Component<{ textToDisplay: string; } & React.HTMLAttributes<HTMLDivElement>> { static propTypes = { textToDisplay: PropTypes.string; } private get HtmlProps(): React.HTMLAttributes<HTMLAnchorElement> { return Object.fromEntries( Object.entries(this.props) .filter(([key]) => !Object.keys(Link.propTypes).includes(key)) ); } public render():JSX.Element { return ( <a {...this.HtmlProps}> {this.props.textToDisplay} </a> ); } } <Link textToDisplay="Search" href="http://google.com" /> 
0
source

This is actually simpler than all the answers above. You just need to follow the example below:

 type Props = { id: number, name: string; // All other props [x:string]: any; } const MyComponent:React.FC<Props> = props => { // Any property passed to the component will be accessible here } 

Hope this helps.

0
source

Source: https://habr.com/ru/post/1258182/


All Articles