References
- React+TypeScript Cheatsheets (link) by @swyx, @ferdaber, @eps1lon, @jsjoeio, and @arvindcheenu
- TypeScript Types or Interfaces for React component props (link) by Ronald Rey
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
, anddefaultProps
) get typechecking and autocompletion- Issues exist involving
defaultProps
- Issues exist involving
children
receives an implicit definition.- Issues exist with this implicit definition of
children
- Issues exist with this implicit definition of
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.