React Typescript Cheatsheet
Last updated on May 2022
Short guide on some of various ways to correctly type components and related types in React. updated for React 18
Typing a functional component
Use React.FC
.
This is typed like this:
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: P, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
So you can use React.FC
on a function (and it expects props
and an optional context
param), and you can pass in a type as your props.
For example this is how to type a functional component...
interface YourComponentProps {
yourName: string
}
export const YourComponent: React.FC<YourComponentProps> = (props) => {
return <div>Hello, {props.yourName}</div>
}
Typing for props.children with React.FC
By itself, it does not add the props.children
property. This page is a really interesting article about why.
There are a few approaches
You can wrap them with PropsWithChildren<YourProps>
:
import React, {PropsWithChildren} from 'react';
interface YourComponentProps {
yourName: string
}
const YourComponent: React.FC<PropsWithChildren<YourComponentProps>> =
(props) => {
return <div>
<h1>
Hello {props.yourName}!
</h1>
<div>
{props.children}
</div>
</div>
}
You could also 'manually' type them with ReactNode, but I'm not a fan:
interface YourComponentProps {
children?: React.ReactNode
};
const YourComponent: React.FC<YourComponentProps> =
({children}) => {
return <div>{children}</div>
}
Note: ReactNode is ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined
.
I think the only times it would be useful is if your component must have a specific type - such as only a ReactElement
(so you can call React.cloneElement(children)
) - then you can use React.ReactElement
as the type.
Less common features of React.FC:
It also has support for setting a few attributes ("implicit props") on the object (functions are objects) - propTypes
, displayName
etc.
As well as easily typing the props
, using React.FC
we can also set a few attributes on the component function object:
import React from 'react';
import * as PropTypes from "prop-types";
interface YourComponentProps {
yourName: string
}
export const YourComponent: React.FC<YourComponentProps> =
(props) => {
return <div>Hello, {props.yourName}</div>
}
YourComponent.displayName = 'this works';
// used when debugging (e.g. in the react dev tools)
YourComponent.propTypes = {
yourName: PropTypes.string,
}; // this works (WeakValidationMap)
YourComponent.contextTypes = {
yourName: PropTypes.string,
} // also works, but is an older feature, not used now.
// see https://legacy.reactjs.org/docs/legacy-context.html
YourComponent.defaultProps = {
yourName: 'your default name'
}
Note: defaultProps
is no longer needed since in modern JS you can set defaults:
import React from 'react';
const YourComponent: React.FC<YourComponentProps> =
({yourName = 'stranger'}) => {
return <div>Hello, {yourName}</div>
}
React.HTMLAttributes - all available props for HTML elements
HTMLAttributes
is all of the possible HTML attributes, plus a few React specific ones (like defaultValue
). Note: this includes className
and htmlFor
(not class
and for
).
(note: the following is truncated - it is around 60 attributes).
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
// React-specific Attributes
defaultChecked?: boolean | undefined;
defaultValue?: string | number | ReadonlyArray<string> | undefined;
suppressContentEditableWarning?: boolean | undefined;
suppressHydrationWarning?: boolean | undefined;
// Standard HTML Attributes
accessKey?: string | undefined;
className?: string | undefined;
// and so on
React.DetailedHTMLProps
note: I have some nicer ways to type components that avoids the messy DetailedHTMLProps - keep reading past this section
DetailedHTMLProps
is defined as:
type DetailedHTMLProps<E extends HTMLAttributes<T>, T>
= ClassAttributes<T> & E;
ClassAttributes
is a (mostly) internal type:
interface Attributes {
key?: Key | null | undefined;
}
interface RefAttributes<T> extends Attributes {
ref?: Ref<T> | undefined;
}
interface ClassAttributes<T> extends Attributes {
ref?: LegacyRef<T> | undefined;
}
So DetailedHTMLProps
is basically a wrapper that adds key
and ref
.
You can use DetailedHTMLProps
when you want to get the props for a standard DOM element, for example if you wanted to wrap an <input>
(in this case with a <div>...</div>
), but wanted the consumer of your component to have all the correct props for an input
element.
import React, {DetailedHTMLProps, InputHTMLAttributes} from 'react';
type InputWrapperProps = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>;
const InputWrapper: React.FC<InputWrapperProps> =
(props: InputWrapperProps) => {
return <div><input {...props} /></div>;
}
const yourInput = <InputWrapper maxLength={5} />
You can get away with a shorter and more readable version:
type InputWrapperProps = InputHTMLAttributes<HTMLInputElement>;
I am sure there are some use cases where DetailedHTMLProps
is useful, if you know of some please let me know and I'll update this.
Adding your own props:
And if you needed to add your own props:
import React, {InputHTMLAttributes} from 'react';
interface InputWrapperProps extends InputHTMLAttributes<HTMLInputElement> {
yourName: string
}
const InputWrapper: React.FC<InputWrapperProps> =
(props) => {
return (
<div>
Hi, {props.yourName}
<input {...props} />
</div>
);
}
const yourInput = <InputWrapper yourName="a5h" maxLength={5}/>
Use ComponentPropsWithoutRef
Another alternative, and I think is my favourite: ComponentPropsWithoutRef
:
import React from 'react'
interface YourComponentProps extends React.ComponentPropsWithoutRef<'input'> {
yourName?: string;
}
const InputWrapper: React.FC<YourComponentProps> = (props) => {
const { yourName, ...otherProps } = props;
return <div>
Hello, {yourName}!
<input {...otherProps} />
</div>;
}
Using ComponentProps
You can also use React.ComponentProps<'input'>
, e.g.
function MyDisabledButton(props: ComponentProps<'button'>) {
return <button
disabled={true}
{...props}
>Disabled button here</button>
}
You can also use it with typeof a component:
function MyComponent(props: { name: string }) {
return <p>Hi {props.name}</p>
}
function CustomMyComponent(props: ComponentProps<typeof MyComponent>) {
return <div>Outer wrapper...<br/>
<MyComponent name={props.name}/>
</div>
}
And of course you can merge this with your component's custom props:
interface CustomMyComponentProps {
wrapperTitle: string
}
function CustomMyComponent(props: ComponentProps<typeof MyComponent> & CustomMyComponentProps) {
return <div>{props.wrapperTitle}<br/>
<MyComponent name={props.name}/>
</div>
}
Return type of functional components
All examples above have an implicit return type. For JSX components I don't see much value in typing their return type, but if you want/need to then you can type it with JSX.Element
Typing hooks
useState typing
State (useState
) has pretty good inferred types. In the next example, score
will be typed to a number
, and you must pass a number
to setScore()
:
import {useState} from "react";
const [score, setScore] = useState(5)
setScore('6') // << error! not a number
// Argument type "6" is not assignable to parameter type ((prevState: number) => number) | number
But if its more complex, you can pass in the typings:
const [score, setScore] = useState<number|string>(5)
setScore('6')
setScore(7)
You can also do it like this (I do not like/recommend this way though, especially as there is no need for it):
const [score, setScore] = useState(5 as number|string)
setScore('6')
setScore(7)
useRef typing
There are two main ways of using the ref you get out of useRef
- for a HTML DOM element, and for everything else.
If you are using it with a HTML DOM ref, then set its initial value to null
. This will type it as a RefObject
(not a MutableRefObject
). React will mutate it when rendering the DOM and will set yourRef.current = theElement
.
import {useRef} from "react";
function SomeComponent() {
const inputRef = useRef(null)
return <input ref={inputRef}
}
You can also type it nicer by saying what it will be used on, e.g.:
import {useRef} from "react";
function SomeComponent() {
const inputRef = useRef<HTMLInputElement>(null!)
return <form onSubmit={() => {
console.log(inputRef.current.value) // typing works as expected
}}>
<input ref={inputRef} />
<input type="submit" />
</form>
}
Apart from using a ref for DOM elements - the only other common use case I've seen is setting it to anything but null
, so you can mutate yourRef.current = 'something new'
It has good implicit typing, similar to useState()
so the following is typed as a string
:
import {useRef} from "react";
function SomeComponent() {
const inputRef = useRef('initial value')
function somethingHandler() {
inputRef.current = 'asdf'
}
// ...
}
Generics on JSX Components
The following works, but its not nice code and I wouldn't want to add it in my code...
It uses <T,>
on purpose - it isn't a typo. It is to avoid the <T>
being parsed as JS.
import React from 'react'
interface YourComponentProps<T = string> {
favNumber: T;
}
// have to pass generic with <T, > to avoid it being parsed
// as runtime JS, but instead it knows its a type.
const YourComponent = <T,>(props: YourComponentProps<T>) => {
return (
<div>
Hello, {props.favNumber}!
</div>
);
};
function ParentString() {
return <YourComponent<string> favNumber={'hundred'} />
}
function ParentNumber() {
return <YourComponent<number> favNumber={123} />
}
// this gives an error - it is expecting true/false:
function ParentNumber2() {
return <YourComponent<boolean> favNumber={123} />
}
Links
- react-html-props for some useful typescript definitions
- @types