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
:
-
ref
andkey
are not available to be used inside a component's implementation, butkey
can always be assigned to any JSX expression in React, andref
s can be assigned to any class-based and intrinsic JSX expressions, as well as function based expressions usingforwardRef
. -
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.
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!