TypeScript and JSX Part IV - Typing the props of a component
Tags: typescript, jsx, react, javascript
Published: 2019-01-25 02:12:43

---
title: TypeScript and JSX Part IV - Typing the props of a component
published: true
description: An explanation of how the TypeScript compiler works with JSX.
tags: typescript,jsx,react,javascript
series: TypeScript and JSX
---

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:

```ts
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']`:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
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`:

- `ref` and `key` are not available to be used inside a component's implementation, but `key` can always be assigned to any JSX expression in React, and `ref`s can be assigned to any class-based and intrinsic JSX expressions, as well as function based expressions using `forwardRef`.
- `defaultProps` allows 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.