Photograph by Artem Sapegin on Unsplash

React with TypeScript

TypeScriptReact

References

Importing

Should use * way of importing

import * as React from 'react';
import * as ReactDOM from 'react-dom';

Typing Function Components and Props

Minimalist Way (implicit return type)

Can use a type alias or an interface for the props

type Props = { propOne: number,  propTwo: string };
const Component = ({ propOne, propTwo }: Props) => (
  <div>{propOne}, {propTwo}</div>
);

OR

interface Props {
  propOne: number;
  propTwo: number;
}

Should I use a type alias or an interface?

TL;DR: For applications, use type aliases. For reusable component libraries/packages (e.g., as found on npm), use interfaces.

It ultimately depends and in most simple use cases it doesn't really matter. However, there is some wisdom in preferring type aliases to interfaces when building applications, while preferring interfaces to type aliases when building reusable component libraries/packages.

Type aliases are good for applications as they mesh better with a functional programming style, a style which React opts for over object oriented programming (OOP). (Interfaces mesh better with an OOP coding style.) Type aliases are also more extensible by way of intersections (type A & type B)and unions (type A | type B), which can be helpful when piecing together the parts of a component's props.

Interfaces are good for reusable component libraries/packages because consumers can redeclare and, thus, reshape those interfaces in their own codebases.

Strongly Typed Way (explicit return type)

Type the component with React.FunctionComponent, aka React.FC

Besides the explicit return type, typing the component in this manner gives you the following:

  • Static properties (displayName, propTypes, and defaultProps) get typechecking and autocompletion
    • Issues exist involving defaultProps
  • children receives an implicit definition.
    • Issues exist with this implicit definition of children

Hooks and Types

useState

You can typically rely on type inference. However, if the hook is initialized with a null value, then explicitly type it, like so...

const [data, setData] = useState<string | null>(null);

useReducer

For initialState, simply assign an object value (no need to explicitly define the type).

const initialState = { one: 1, two: "two" };

For actions, Use Discriminated Unions, like so...

type REDUCER_ACTION =
  | { type: 'action_one'; payload: boolean }
  | { type: 'action_two'; payload: string }
  ...

For the reducer function, use the typeof keyword to type the state and, as usual, return a new instance of the state using a switch statement.

function reducer(state: typeof initialState, action: REDUCER_ACTION) {
  switch (action.type) {
    case 'action_one':
      return { one: 5, two: "three" };
    case 'action_two':
      return { ...two, one: 7 };
    default:
      throw new Error('Unsupported action type');
  }
}

useEffect

There are no typing strategies specific to the useEffect hook because there are no types to consider for the hook itself.

useRef

If you will manage the instance of the ref yourself (i.e., if you plan to change its .current value), then use a mutable form of typing for the useRef hook.

const refIWillUse = useRef<boolean>(false);
const elementRefIWillMutate = useRef<HTMLElement | null>(null);

// You will probably do this in a useEffect hook
refIWillUse.current = true;
elementRefIWillMutate.current.blur();

If the ref will be passed down to a React component to manage, then make the ref read only. That is, useRef will now return a read only RefObject<T> instead of a MutableRefObject<T>.

const readOnlyRef = useRef<HTMLElement>(null);

Custom Hooks

If you intend to return a tuple from a custom hook, then either type the returned value as a tuple or as a const assertion. This prevents TS from making an incorrect inference about the return type. For example, say you want to return tuple with a first item of type boolean and the second of type number, then the return option are...

... as a tuple

return [boolVal, numVal] as [boolean, number];

... as a const assertion

return [boolVal, numVal] as const;

If a custom hook returns more than two values, then the return type should be an object and not an array/tuple.