react README

The project's README from github.com/typescript-cheatsheets/react. As one markdown file: README.md.

React TypeScript Cheatsheet

Cheatsheet for using React with TypeScript.


react + ts logo

Web docs | Contribute! | Ask!

:wave: This repo is maintained by @eps1lon and @filiptammergard. We're so happy you want to try out React with TypeScript! If you see anything wrong or missing, please file an issue! :+1:


All Contributors | Discord

The Cheatsheet is focused on helping React devs use TypeScript effectively:


Cheatsheet

Table of Contents

Expand Table of Contents

Introduction

Prerequisites

You can use this cheatsheet for reference at any skill level, but basic understanding of React and TypeScript is assumed. Here is a list of prerequisites:

In the cheatsheet we assume you are using the latest versions of React and TypeScript.

React and TypeScript starter kits

React has documentation for how to start a new React project with some of the most popular frameworks. Here's how to start them with TypeScript:

If you just want a client-side single-page app without a framework, Vite is the most common choice:

Try React and TypeScript online

There are some tools that let you run React and TypeScript online, which can be helpful for debugging or making sharable reproductions.

Getting Started

Function Components

These can be written as normal functions that take a props argument and return a JSX element.

// Declaring type of props - see "Typing Component Props" for more examples
type AppProps = {
  message: string;
}; /* use `interface` if exporting so that consumers can extend */

// Easiest way to declare a Function Component; return type is inferred.
const App = ({ message }: AppProps) => <div>{message}</div>;

// You can choose to annotate the return type so an error is raised if you accidentally return some other type
const App = ({ message }: AppProps): React.JSX.Element => <div>{message}</div>;

// You can also inline the type declaration; eliminates naming the prop types, but looks repetitive
const App = ({ message }: { message: string }) => <div>{message}</div>;

// Alternatively, you can use `React.FunctionComponent` (or `React.FC`), if you prefer.
// With latest React types and TypeScript 5.1. it's mostly a stylistic choice, otherwise discouraged.
const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
  <div>{message}</div>
);
// or
const App: React.FC<AppProps> = ({ message }) => <div>{message}</div>;

Tip: You might use Paul Shen's VS Code Extension to automate the type destructure declaration (incl a keyboard shortcut).

Why is React.FC not needed? What about React.FunctionComponent/React.VoidFunctionComponent?

You may see this in many React+TypeScript codebases:

const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
  <div>{message}</div>
);

However, the general consensus today is that React.FunctionComponent (or the shorthand React.FC) is not needed. If you're still using React 17 or TypeScript lower than 5.1, it is even discouraged. This is a nuanced opinion of course, but if you agree and want to remove React.FC from your codebase, you can use this jscodeshift codemod.

Some differences from the "normal function" version:

  • React.FunctionComponent is explicit about the return type, while the normal function version is implicit (or else needs additional annotation).

  • It provides typechecking and autocomplete for static properties like displayName, propTypes, and defaultProps.

    • Note that there are some known issues using defaultProps with React.FunctionComponent. See this issue for details. We maintain a separate defaultProps section you can also look up.
  • In the future, it may automatically mark props as readonly, though that's a moot point if the props object is destructured in the parameter list.

In most cases it makes very little difference which syntax is used, but you may prefer the more explicit nature of React.FunctionComponent.

Hooks

Hooks are supported in @types/react from v16.8 up.

useState

Type inference works very well for simple values:

const [state, setState] = useState(false);
// `state` is inferred to be a boolean
// `setState` only takes booleans

If you need to use a complex type that you've relied on inference for, you can use typeof to capture the inferred type.

However, many hooks are initialized with null-ish default values, and you may wonder how to provide types. Explicitly declare the type, and use a union type:

const [user, setUser] = useState<User | null>(null);

// later...
setUser(newUser);

You can also use type assertions if a state is initialized soon after setup and always has a value after:

const [user, setUser] = useState<User>({} as User);

// later...
setUser(newUser);

This temporarily "lies" to the TypeScript compiler that {} is of type User. You should follow up by setting the user state — if you don't, the rest of your code may rely on the fact that user is of type User and that may lead to runtime errors.

useCallback

You can type the useCallback just like any other function.

const memoizedCallback = useCallback(
  (param1: string, param2: number) => {
    console.log(param1, param2)
    return { ok: true }
  },
  [...],
);
/**
 * VSCode will show the following type:
 * const memoizedCallback:
 *  (param1: string, param2: number) => { ok: boolean }
 */

Note that for React < 18, the function signature of useCallback typed arguments as any[] by default:

function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: DependencyList,
): T;

In React >= 18, the function signature of useCallback changed to the following:

function useCallback<T extends Function>(callback: T, deps: DependencyList): T;

Therefore, the following code will yield "Parameter 'e' implicitly has an 'any' type." error in React >= 18, but not <17.

// @ts-expect-error Parameter 'e' implicitly has 'any' type.
useCallback((e) => {}, []);
// Explicit 'any' type.
useCallback((e: any) => {}, []);

useReducer

You can use Discriminated Unions for reducer actions. Don't forget to define the return type of reducer, otherwise TypeScript will infer it.

import { useReducer } from "react";

const initialState = { count: 0 };

type ACTIONTYPE =
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: string };

function reducer(
  state: typeof initialState,
  action: ACTIONTYPE,
): typeof initialState {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
        -
      </button>
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
    </>
  );
}

View in the TypeScript Playground

Usage with Reducer from redux

In case you use the redux library to write reducer function, It provides a convenient helper of the format Reducer<State, Action> which takes care of the return type for you.

So the above reducer example becomes:

import { Reducer } from 'redux';

export function reducer: Reducer<AppState, Action>() {}
Providing explicit types for useReducer

In most cases, type inference for useReducer should work reliably. When inference fails, the state and action types can be explicitly provided using the following syntax, where the action type is wrapped in a single-element tuple.

const [state, dispatch] = useReducer<typeof initialState, [ACTIONTYPE]>(
  reducer,
  initialState,
);

useEffect / useLayoutEffect

Both of useEffect and useLayoutEffect are used for performing side effects and return an optional cleanup function which means if they don't deal with returning values, no types are necessary. When using useEffect, take care not to return anything other than a function or undefined, otherwise both TypeScript and React will yell at you. This can be subtle when using arrow functions:

function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(
    () =>
      setTimeout(() => {
        /* do stuff */
      }, timerMs),
    [timerMs],
  );
  // bad example! setTimeout implicitly returns a number
  // because the arrow function body isn't wrapped in curly braces
  return null;
}
Solution to the above example
function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(() => {
    setTimeout(() => {
      /* do stuff */
    }, timerMs);
  }, [timerMs]);
  // better; use the void keyword to make sure you return undefined
  return null;
}

useRef

useRef always returns a RefObject<T> in current @types/react. An initial value is required, and the returned .current is typed based on it. (MutableRefObject is deprecated and only kept for backwards compatibility.)

Option 1: DOM element ref

To access a DOM element: provide the element type as a generic and pass null as the initial value. React manages .current for you, and TypeScript expects you to pass this ref to an element's ref prop:

function Foo() {
  // - If possible, prefer as specific as possible. For example, HTMLDivElement
  //   is better than HTMLElement and way better than Element.
  // - Technical-wise, this returns RefObject<HTMLDivElement>
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Note that ref.current may be null. This is expected, because you may
    // conditionally render the ref-ed element, or you may forget to assign it
    if (!divRef.current) throw Error("divRef is not assigned");

    // Now divRef.current is sure to be HTMLDivElement
    doSomethingWith(divRef.current);
  });

  // Give the ref to an element so React can manage it for you
  return <div ref={divRef}>etc</div>;
}

If you are sure that divRef.current will never be null, it is also possible to use the non-null assertion operator !:

const divRef = useRef<HTMLDivElement>(null!);
// Later... No need to check if it is null
doSomethingWith(divRef.current);

Note that you are opting out of type safety here - you will have a runtime error if you forget to assign the ref to an element in the render, or if the ref-ed element is conditionally rendered.

Tip: Choosing which HTMLElement to use

Refs demand specificity - it is not enough to just specify any old HTMLElement. If you don't know the name of the element type you need, you can check lib.dom.ts or make an intentional type error and let the language service tell you:

image

Option 2: Mutable value ref

To hold a mutable value across renders without re-rendering on change: pass the initial value you want — React doesn't manage .current for you here, you write to it manually.

function Foo() {
  const intervalRef = useRef<number | null>(null);

  useEffect(() => {
    intervalRef.current = window.setInterval(() => {
      /* ... */
    }, 1000);
    return () => {
      if (intervalRef.current !== null) clearInterval(intervalRef.current);
    };
  }, []);

  return (
    <button
      onClick={() => {
        /* clearInterval the ref */
      }}
    >
      Cancel timer
    </button>
  );
}
See also

react (typescript-cheatsheets/react)

Searching Database…

47.1K ·GitHub
这是干什么的?一份持续维护的速查表,面向想上手 TypeScript 的有经验 React 开发者,收录了大量带有明确倾向性的最佳实践,还有可以直接复制进代码里的示例。
它是需要装到我项目里的库,还是一份阅读材料?是用来读的:它是文档速查表,内容放在 README 和一个 Web 文档网站上,没有任何东西需要安装。
它针对哪些 React 和 TypeScript 版本?它假定你用的是 React 和 TypeScript 的最新版本;hooks 的类型支持从 @types/react v16.8 起就有了。
如果我还在学 React 本身,它对我有帮助吗?不适合当入门教程:不管什么水平都可以拿来当参考,但它假定你已经对 React 和 TypeScript 有基本了解。
它是给想试 TypeScript 的 React 开发者,还是给想试 React 的 TypeScript 开发者的?面向正在学习 TypeScript 的 React 开发者。它先讲基础的 TS 类型和项目配置,也涵盖高级泛型类型,适合编写可复用类型工具以及 React+TS 库的人。
它有没有讲怎么搭建一个新的 React + TypeScript 项目?有:Next.js(npx create-next-app@latest --ts)、Remix(npx create-remix@latest)、Gatsby(npm init gatsby --ts)、Expo(npx create-expo-app -t with-typescript),以及用 Vite(npm create vite@latest my-app -- --template react-ts)创建纯客户端 SPA。
它为什么认为不需要用 React.FC?它的立场是 React.FunctionComponent(React.FC)并非必需,如果你用的是 React 17 或低于 5.1 的 TypeScript,甚至不建议使用;文中还链接了一个 jscodeshift codemod,用来批量移除它。
它涵盖了哪些 hooks?useState、useCallback、useReducer、useEffect/useLayoutEffect、useRef、useImperativeHandle 以及自定义 hooks,还介绍了诸如用联合类型表示可能为 null 的状态、用可辨识联合处理 reducer action 之类的模式。
类型到底用 type 还是 interface——它有站队吗?它有一个专门的 Types or Interfaces 章节,包含 TL;DR、补充建议和一张对比表,帮你在给 props 标注类型等场景下决定该用哪个。
Size
3.6 MB
Stars
47.1K
Updated
CommitSep 09
Age
8y
Commits · 90 Days