Skip to content

[discussion] dynamic merge properties to type(or interface) by its specific key #28041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pd4d10 opened this issue Oct 22, 2018 · 6 comments
Closed
Labels
Domain: JSX/TSX Relates to the JSX parser and emitter Question An issue which isn't directly actionable in code

Comments

@pd4d10
Copy link

pd4d10 commented Oct 22, 2018

Actually it is a question, but I think it may refers to type system of TypeScript, so I post it here.

The problem is how to dynamic merge properties to type(or interface) by its specific key.

Assuming we are creating a React component named Button. It takes a component prop, and will use this value as component actually rendered. Its implementation may looks like this:

interface ButtonProps {
  component: keyof React.ReactHTML
}

class Button extends React.Component<ButtonProps> {
  render() {
    const { component: Component, ...rest } = this.props
    // give it a button style
    // `component`'s value could be button, a, input, etc
    return <Component {...rest} />
  }
}

we need native HTML element's props merged to ButtonProps.

For example, the usage examples below should be all type safe.

<Button component="button" type="submit">A Button</a>
<Button component="a" href="/">A Link Button</a>
<Button component="input" type="submit" value="An input button" />

Which means, if component === 'a', ButtonProps should be

{ component: React.ReactHTML } & React.AnchorHTMLAttributes<HTMLAnchorElement>

if component === 'input', ButtonProps should be

{ component: React.ReactHTML } & React.InputHTMLAttributes<HTMLInputElement>

How to figure this out in TypeScript?

@pd4d10
Copy link
Author

pd4d10 commented Oct 22, 2018

My thoughts:

type PropsOfHtmlTag<T extends keyof React.ReactHTML> =
React.ReactHTML[T] extends React.DetailedHTMLFactory<infer R, any>
  ? R
  : never

interface ButtonProps {
  component: keyof React.ReactHTML
}

type DynamicProps = PropsOfHtmlTag<ButtonProps['component']>

What we need is to merge ButtonProps and DynamicProps

@ghost
Copy link

ghost commented Oct 22, 2018

I think this is possible:

import * as React from "React";

type AttributesForTag = { [K in keyof React.ReactHTML]: React.ReactHTML[K] extends React.DetailedHTMLFactory<infer Attr, any> ? Attr : never; };

class Button<K extends keyof AttributesForTag> extends React.Component<{ component: K } & AttributesForTag[K]> {
    render() {
        const { component, ...rest } = this.props as any;
        return React.createElement(component, rest);
    }
}

const x = <Button component="button" type="submit" />

Is it also possible to just make the element be one of the props?

class Button2 extends React.Component<{ component: JSX.Element }> {
    render() { return this.props.component; }
}
<Button2 component={<button type="submit" />} />

@pd4d10
Copy link
Author

pd4d10 commented Oct 23, 2018

@Andy-MS Thanks for your response. I tried your example in VSCode:

2018-10-23 10 47 53

Since HTML tag a has no value, I think the last line should throw an error but not. Tried to go to value key's definition then it shows:

2018-10-23 10 55 07

Seems it merges all HTML tags' attributes? Then we got multiple definitions

@nkappler
Copy link

it works for me:

image

@ghost
Copy link

ghost commented Oct 23, 2018

@pd4d10 Darn, it must be inferring K = string and allowing all elements. This has come up before, see #27808. You'll at least get correct behavior if you remember to provide an explicit type argument: <Button<"a"> component="a" value="submit" />;.

@weswigham weswigham added Question An issue which isn't directly actionable in code Domain: JSX/TSX Relates to the JSX parser and emitter labels Oct 23, 2018
@pd4d10
Copy link
Author

pd4d10 commented Oct 26, 2018

@nkappler @Andy-MS Thanks. Closing since the question is answered clearly

@pd4d10 pd4d10 closed this as completed Oct 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: JSX/TSX Relates to the JSX parser and emitter Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants