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} />
}
© 2019-2023 a5h.dev.
All Rights Reserved. Use information found on my site at your own risk.