TypeScript and JSX Part II - What can create JSX?
Tags: typescript, jsx, react, javascript
Published: 2019-01-17 03:46:48

---
title: TypeScript and JSX Part II - What can create JSX?
published: true
description: An explanation of how the TypeScript compiler works with JSX.
tags: typescript,jsx,react,javascript
series: TypeScript and JSX
---

In Part I of this series, we learned basic JSX syntax and some basic terminology when discussing the abstract syntax tree of JSX expressions. Let's now dive into how TypeScript checks the type validity of JSX expressions.

Not everything can be valid JSX constructors, you can't just shove any random value into the opening tag of a JSX expression:
```tsx
// bad! it's actually 'a'
const badAnchor = <anchor href='dev.to'>Go to dev.to!</anchor>

// bad! it's not a function!
const MyComponent = {}
const badFunctionElement = <MyComponent>Hi!</MyComponent>

// bad! it's not something that can render!
class MyClassComponent {
  constructor(props: any) { this.props = props }
}
const badClassElement = <MyClassComponent>Hi!</MyClassComponent>
```

So how does TypeScript know when something is a valid JSX element constructor? The answer lies in the magical `JSX` namespace. Remembering how the `jsxFactory` compiler option (or the `@jsx` pragma) works, we have that the factory function for React is `React.createElement`. You might also be using some other library, where the factory function is often called `h`:
```tsx
// @jsx React.createElement
import React from 'react'

// @jsx h
import { h } from 'preact'
```

TypeScript will attempt to look up a namespace called `JSX` under the factory function and fallback to a global one if none is found:
- for factory functions that are under another namespace, like `React.createElement`, it will look for `React.JSX`
- for factory functions that are just a naked identifier, like `h`, it will look for `h.JSX`
- if no `JSX` namespace is found, it looks for a global `JSX` namespace

The React type definitions declares a global `JSX` namespace, though that's not a good idea and we should change that soon 😅.

So what's the use of the `JSX` namespace? TypeScript looks for specific interfaces under it to figure out what's acceptable for each type of JSX element constructor:
- for "intrinsic" element constructors (lower-case tag name), it looks if a property with that same key exists under `JSX.IntrinsicElements`.
- for function element constructors, it checks if its return type is assignable to the `JSX.Element` interface.
- for class-based element constructors, it checks if its instance type is assignable to the `JSX.ElementClass` interface.

Let's look at each case in detail:

#### Intrinsic Element Constructors

If your JSX namespace looks like this:
```ts
namespace JSX {
  interface IntrinsicElements {
    a: HTMLAttributes<HTMLAnchorElement>
    button: HTMLAttributes<HTMLButtonElement>
    div: HTMLAttributes<HTMLElement>
    span: HTMLAttributes<HTMLElement>
  }
}
```
Then you can render these elements:
```tsx
const validIntrinsicElements = [<a />, <button />, <div />, <span />]
// error properties 'select', 'main', and 'nav' do not exist on type 'JSX.IntrinsicElements'
const invalidIntrinsicElements = [<select />, <main />, <nav />]
```

We'll talk about what the types of the properties themselves actually mean in the next part of the series.

#### Function Element Constructors

If your JSX namespace looks like this:
```ts
namespace JSX {
  interface Element {
    key?: string
    type: string | (() => any)
    props: { [propName: string]: any }
  }
}
```

And you have a function like this:
```ts
function MyComponent(props: any) {
  return {
    type: MyComponent,
    props: props
  }
}
```

Then you have a valid constructor! Because its return type is assignable to `JSX.Element`:
```tsx
const myFunctionElement = <MyComponent /> // good to go!
```

How is it though, that when you have a function without its return type annotated, but it returns JSX, it's still okay? That's because TypeScript will treat any JSX expression's type to be the same type as `JSX.Element`!
```tsx
function MyComponent() {
  return <div>Hi!</div>
}

const myFunctionElement = <MyComponent /> // still okay

const nakedElement = <div>hi!</div>
type NakedElementType = typeof nakedElement // the type is JSX.Element
```

An astute reader will notice that this has some odd pitfalls when it comes to what React allows you to return from a component. Remember that React allows you to return arrays, strings, numbers, and booleans from a component, which it will happily render:
```tsx
function MyStringFragment() {
  return ['a', 'b', 'c'] // type is string[]
}

const myFragment = <MyStringFragment /> // TS error!
```

Uh oh, this is an unfortunate limitation of the type checker; if we want to get the check to pass, we need to assert the type of the return value:
```tsx
function MyStringFragment() {
  return ['a', 'b', 'c'] as any as JSX.Element
}

const myFragment = <MyStringFragment /> // good now!
```

There is an open issue for the TypeScript repo that will hopefully resolve this issue in the future: https://github.com/Microsoft/TypeScript/issues/14729.

#### Class Element Constructors

If your JSX namespace looks like this:
```ts
namespace JSX {
  interface ElementClass {
    render(): any
  }
}
```

And you have a class like this:
```ts
class Component {
  constructor(props: any) {
    this.props = props
  }
  
  render() {
    return { obviouslyNotAnElement: 'fooled ya!' }
  }

  someOtherMethod(): string
}
```

Then you have a valid constructor! Because its instance type is assignable to `JSX.ElementClass`:
```ts
const myComponentInstance = new Component({})

type myComponentInstanceType = {
  render(): { obviouslyNotAnElement: string }
  someOtherMethod(): string
}

type ComponentInstanceType = {
  render(): any
}
```

Obviously the real React type is different, but that's why we always `extend` from `React.Component`, because this is what it roughly looks like in React's types:
```ts
namespace React {
  type Renderable = JSX.Element | JSX.Element[] | number | string | boolean | null
  class Component {
    /* other methods like setState, componentDidUpdate, componentDidMount, etc */
    render(): Renderable
  }

  namespace JSX {
    interface ElementClass {
      render(): Renderable
    }
  }
}
```

And now any class that you declare that extends `React.Component` will be a valid constructor!

In summary: before we even talk about props, TypeScript has to check if a component is actually a valid JSX constructor, otherwise it rejects it when you try to use it in a JSX expression.

In the next post in this series, we'll be talking about what TypeScript considers to be valid *attributes* given a specific JSX expression (remember: attributes are the props you give to a JSX expression, like HTML element attributes).