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.htmlYourComponent.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 AttributesdefaultChecked?: boolean | undefined;defaultValue?: string | number | ReadonlyArray<string> | undefined;suppressContentEditableWarning?: boolean | undefined;suppressHydrationWarning?: boolean | undefined;// Standard HTML AttributesaccessKey?: 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 <buttondisabled={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