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>
  );
}

Note

These sandboxes can handle TypeScript code, but they do not run the type-checker. This means you can amend the TypeScript sandboxes to learn, but you won’t get any type errors or warnings. To get type-checking, you can use the TypeScript Playground or use a more fully-featured online sandbox.

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 by useReducer 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: