Using TypeScript
TypeScript is a popular way to add type definitions to JavaScript codebases. Out of the box, TypeScript supports JSX and you can get full React support by adding @types/react-dom
to your project.
All of the production-grade frameworks mentioned in Start a New React Project offer support for using TypeScript with React, we recommend consulting their documentation for more information on setup. This guide assumes you have your project configured to support writing *.tsx
TypeScript React files, and have finished the Quick Start guide.
TypeScript with React Components
Writing TypeScript with React is very similar to writing JavaScript with React. The key difference when working with a component is that you can provide types for your component’s props. These types can be used for correctness checking and providing inline documentation in editors.
Taking the MyButton
functional component from the Quick Start guide, we can add a type describing the title
for the button:
function MyButton({ title }: { title: string }) { return ( <button>{title}</button> ); } export default function MyApp() { return ( <div> <h1>Welcome to my app</h1> <MyButton title="I'm a button" /> </div> ); }
This inline syntax is the simplest way to provide types for a functional component, though once you start to have a few fields to describe it can become unwieldy. Instead, you can use an interface
or type
to describe the component’s props:
interface MyButtonProps { /** The text to display inside the button */ title: string; /** Whether the button can be interacted with */ disabled: boolean; } function MyButton({ title, disabled }: MyButtonProps) { return ( <button disabled={disabled}>{title}</button> ); } export default function MyApp() { return ( <div> <h1>Welcome to my app</h1> <MyButton title="I'm a disabled button" disabled={true}/> </div> ); }
The type describing your component’s props can be as simple or as complex as you need, though they should probably be an object type. You can learn about how TypeScript describes objects in Object Types but you may also be interested in using Union Types to describe a prop that can be one of a few different types and the Creating Types from Types guide for more advanced use cases.
Hooks
The type definitions from @types/react-dom
include types for the built-in hooks, so you can use them in your components without any additional setup. They are built to take into account the code you write in your component, so you will get inferred types a lot of the time and ideally do not need to handle the minutiae of providing the types. However, we can look at a few examples of how to provide types for hooks.
useState
The useState
hook will re-use the value passed in as the initial state to determine what the type of the value should be. For example:
const [enabled, setEnabled] = useState(false);
Will assign the type of boolean
to enabled
, and setEnabled
will be a function accepting either a boolean
argument, or a function that returns a boolean
. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the useState
call:
const [enabled, setEnabled] = useState<boolean>(false);
This isn’t very useful in this case, but a common case where you may want to provide a type is when you have a union type. For example, status
here can be one of a few different strings:
type Status = "idle" | "loading" | "success" | "error";
const [status, setStatus] = useState<Status>("idle");
Or, as recommended in Principles for structuring state, you can group related state as an object and describe the different possibilities via object types:
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };
const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });
useReducer
The useReducer
hook is a more complex hook that takes a reducer function and an initial state. The types for the reducer function are inferred from the initial state. You can optionally provide a type argument to the useReducer
call to provide a type for the state, but it is often better to set the type on the initial state instead:
import {useReducer} from 'react'; interface State { count: number }; type CounterAction = | { type: "reset" } | { type: "setCount"; value: State["count"] } const initialState: State = { count: 0 }; function stateReducer(state: State, action: CounterAction): State { switch (action.type) { case "reset": return initialState; case "setCount": return { ...state, count: action.value }; default: throw new Error("Unknown action"); } } export default function App() { const [state, dispatch] = useReducer(stateReducer, initialState); const addFive = () => dispatch({ type: "setCount", value: state.count + 5 }); const reset = () => dispatch({ type: "reset" }); return ( <div> <h1>Welcome to my counter</h1> <p>Count: {state.count}</p> <button onClick={addFive}>Add 5</button> <button onClick={reset}>Reset</button> </div> ); }
We are using TypeScript in a few key places:
interface State
describes the shape of the reducer’s state.type CounterAction
describes the different actions which can be dispatched to the reducer.const initialState: State
provides a type for the initial state, and also the type which is used byuseReducer
by default.stateReducer(state: State, action: CounterAction): State
sets the types for the reducer function’s arguments and return value.
A more explicit alternative to setting the type on initialState
is to provide a type argument to useReducer
:
const initialState = { count: 0 };
export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}
useContext
The useContext
hook is a technique for passing data down the component tree without having to pass props through components. It is used by creating a provider component and often by creating a hook to consume the value in a child component.
The type of the value provided by the context is inferred from the value passed to the createContext
call:
import { createContext, useContext, useState } from 'react'; type Theme = "light" | "dark" | "system"; const ThemeContext = createContext<Theme>("system"); const useGetTheme = () => useContext(ThemeContext); export default function MyApp() { const [theme, setTheme] = useState<Theme>('light'); return ( <ThemeContext.Provider value={theme}> <MyComponent /> </ThemeContext.Provider> ) } function MyComponent() { const theme = useGetTheme(); return ( <div> <p>Current theme: {theme}</p> </div> ) }
This technique works when you have an obvious default value - but often with context you do not, and in those cases null
is an appropriate default value. However, you likely would not want null
to be in the type when consuming the type, our recommendation is to have the hook do a runtime check for it’s existence and throw an error when not present:
import { createContext, useContext, useState, useMemo } from 'react';
// This is a simpler example, but you can imagine a more complex object here
type ComplexObject = {
kind: string
};
// The context is created with `| null` in the type, to accurately reflect the default value.
const Context = createContext<ComplexObject | null>(null);
// The `| null` will be removed via the check in the hook.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}
export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);
return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}
function MyComponent() {
const object = useGetComplexObject();
return (
<div>
<p>Current object: {object.kind}</p>
</div>
)
}
useRef
The useRef
hook returns a mutable ref object whose .current
property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.
useRef
is often used to access the underlying DOM element of a component, but it can also be used to store any mutable value.
When interacting with the DOM, the type of the ref should be set to the type of the underlying DOM element. The naming rule is HTML
+ the name of the element + Element
, for example HTMLDivElement
or HTMLButtonElement
. You can see the full list from TypeScript 5.1 here. These are provided globally by the “DOM” lib, which is included by default in TypeScript projects.
import { useRef } from 'react'; export default function Form() { const inputRef = useRef<HTMLButtonElement | null>(null); function handleClick() { // The ?. is used because of the `| null` above. inputRef?.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
useMemo
/ useCallback
The useMemo
and useCallback
hooks follow the same pattern with determining their type from the parameter passed to them. If needed you can pass a type argument to them to explicitly set the type.
useCallback
requires adding your
useEffect
The useEffect
hook is used to perform side effects in a component. It is called after every render by default, but can be configured to only run when certain values change. The types for this hook allow for either returning a cleanup function or not returning anything.
Useful Types
There is quite an expansive set of types which come from the @types/react
package, it is worth a read when you feel comfortable with how React and TypeScript interact. You can find them in React’s folder in DefinitelyTyped. We will cover a few of the more common types here.
DOM Events
When working with DOM events in React, the type of the event is inferred from the event handler. However, when you want to extract a function to be passed to an event handler, you will need to explicitly set the type of the event.
import { useState } from 'react'; export default function Form() { const [value, setValue] = useState("Change me"); function handleChange(event: React.ChangeEvent<HTMLInputElement>) { setValue(event.target.value); } return ( <> <input value={value} onChange={handleChange} /> <p>Value: {value}</p> </> ); }
There are many types of events provided in the React types - the full list can be found here which is based on the most popular events from the DOM. If you need to use an event that is not included in this list, you can use the React.SyntheticEvent
type, which is the base type for all events.
Children
There are three common paths to describing the children of a component. The first is to use the React.ReactNode
type, which is a union of all the possible types that can be passed as children in JSX:
interface ModalRendererProps {
title: string;
children: React.ReactNode;
}
The second is to use the React.ReactElement
type, which is only JSX elements and not JavaScript primitives like strings or numbers:
interface ModalRendererProps {
title: string;
children: React.ReactElement;
}
The third is to use React.PropsWithChildren
which is a utility type that takes an object type and adds a optional children
field of React.ReactNode
to it:
type ModalRendererProps = React.PropsWithChildren<{
title: string;
}>
Note, that you cannot use TypeScript to describe that the children are a certain type of elements. You can see all three of these in action with the type-checker in this TypeScript playground.
Style Props
When using inline styles in React, you can use React.CSSProperties
to describe the object passed to the style
prop. This type is a union of all the possible CSS properties, and is a good way to ensure you are passing valid CSS properties to the style
prop, and to get auto-complete in your editor.
interface MyComponentProps {
style: React.CSSProperties;
}
Further learning
This guide has covered the basics of using TypeScript with React, but there is a lot more to learn. We recommend the following resources:
-
The TypeScript handbook is the official documentation for TypeScript, and covers most key language features.
-
The TypeScript release notes covers a each new features in-depth.
-
React TypeScript Cheatsheet is a community-maintained cheatsheet for using TypeScript with React, covering a lot of useful edge cases and providing more breadth than this document.
-
TypeScript Community Discord is a great place to ask questions and get help with TypeScript and React issues.