In the last post we learned how TypeScript type checks JSX children with respect to a constructor's props. This time we'll delve deeper into the rest of a component's props and how those are used for typechecking which are valid when creating JSX.
TypeScript treats intrinsic, function, and class components differently when figuring out which attributes can be assigned to a JSX expression constructed by these components.
- for intrinsic element constructors (lower-case tag name), it looks at the type of the same property name in 
JSX.IntrinsicElements - for function element constructors, it looks at the type of the first parameter in the call signature
 - for class-based element constructors, it looks at the type of the instance property that has the same name under 
JSX.ElementAttributesProperty, and if that doesn't exist, it will look at the type of the first parameter in the constructor call signature 
Let's look at each case in detail:
Intrinsic Element Constructors
If your JSX namespace looks like this:
interface HTMLAttributes<T> {
  children?: ReactNode
  className?: string
  id?: string
  onClick?(event: MouseEvent<T>): void
  ref?: { current?: T }
}
namespace JSX {
  interface IntrinsicElements {
    a: HTMLAttributes<HTMLAnchorElement>
    button: HTMLAttributes<HTMLButtonElement>
    div: HTMLAttributes<HTMLElement>
    span: HTMLAttributes<HTMLElement>
  }
}
Then for an anchor element, the available attributes you can give an <a /> tag equivalent to JSX.IntrinsicElements['a']:
interface AnchorProps {
  children?: ReactNode
  className?: string
  id?: string
  onClick?(event: MouseEvent<HTMLAnchorElement>): void
  ref?: { current?: HTMLAnchorElement }
}
declare const props: AnchorProps
const myAnchor = <a {...props} />
Function Element Constructors
If your component looks like this:
interface Props {
  onClick?(event: MouseEvent<HTMLButtonElement>): void
  disabled?: boolean
  label: string
}
function MyButton(
  props: Props & { children?: ReactNode },
  some?: any,
  other?: any,
  parameters?: any
) {
  return <button />
}
Then the available attributes are Props along with { children?: ReactNode }, because that's the type of the first parameter in the function. Note that TypeScript will respect optional and required properties in the type of the props as well:
const button = <MyButton /> // error because label is marked as required in Props!
Class Element Constructors
If your class looks like this, and you have a JSX namespace like this:
interface Props {
  onClick?(event: MouseEvent<HTMLButtonElement>): void
  disabled?: boolean
  label: string
}
class MyComponent {
  _props: Props
  constructor(props: Props & { children?: ReactNode }) {
    this.props = props
  }
  render() {
    return <button />
  }
}
namespace JSX {
  interface ElementClass {
    render(): any
  }
  interface ElementAttributesProperty {
    _props: {}
  }
}
Then the available attributes for MyComponent are Props (note that this one cannot have children), because the instance type of MyComponent has a property called _props, which is the same as the property name inside JSX.ElementAttributesProperty. If that interface in the JSX namespace wasn't there, it would instead look at the first parameter's type in the constructor, which is Props with { children?: ReactNode }.
This covers all of the "internal" props that a component can use within it. In React, however, we have a concept of "external" props which is the actual contract of what you can pass into a JSX expression constructed by the component. An example of how external props differ from internal props would be ref and key, as well as defaultProps:
- 
refandkeyare not available to be used inside a component's implementation, butkeycan always be assigned to any JSX expression in React, andrefs can be assigned to any class-based and intrinsic JSX expressions, as well as function based expressions usingforwardRef. - 
defaultPropsallows a specific prop to always be defined inside a component's implementation, but optional when assigning that same prop in a JSX expression of that component. 
In the next post we will learn how TypeScript allows this to happen using some more JSX namespace magic.
    
Top comments (1)
Great posts and I learned a lot. When are you gonna publish the next post? Hoping that you can publish it and patiently waiting!
Kudos!