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.