Cheatsheets for experienced React developers getting started with TypeScript
Basic | Advanced | Migrating | HOC | 中文翻译 | Español | Contribute! | Ask!
This Advanced Cheatsheet helps show and explain advanced usage of generic types for people writing reusable type utilities/functions/render prop/higher order components and TS+React libraries.
- It also has miscellaneous tips and tricks for pro users.
- Advice for contributing to DefinitelyTyped
- The goal is to take full advantage of TypeScript.
Creating React + TypeScript Libraries
The best tool for creating React + TS libraries right now is tsdx
. Run npx tsdx create
and select the "react" option. You can view the React User Guide for a few tips on React+TS library best practices and optimizations for production.
- Be sure to also check
basarat
's guide for library tsconfig settings. - From the Angular world, check out https://github.com/bitjson/typescript-starter
Expand Table of Contents
- Section 0: Utility Types
- Section 1: Reusable Components/Type Utilities
- Higher Order Components
- Render Props
- Conditionally Rendering Components
as
props (passing a component to be rendered)- Generic Components
- Typing a Component that Accepts Different Props
- Props: One or the Other but not Both
- Props: Must Pass Both
- Props: Can Optionally Pass One Only If the Other Is Passed
- Omit attribute from a type
- Type Zoo
- Extracting Prop Types of a Component
- Handling Exceptions
- Third Party Libraries
- Section 2: Useful Patterns by TypeScript Version
- Section 3: Misc. Concerns
- Section 4: @types/react and @types/react-dom APIs
We will assume knowledge of utility types covered in the sister project typescript-utilities-guide
. Look up libraries included there as well for your typing needs.
There is now a dedicated HOC cheatsheet you can refer to get some practice on HOCs.
Sometimes you will want to write a function that can take a React element or a string or something else as a prop. The best Type to use for such a situation is React.ReactNode
which fits anywhere a normal, well, React Node would fit:
export interface Props {
label?: React.ReactNode;
children: React.ReactNode;
}
export const Card = (props: Props) => {
return (
<div>
{props.label && <div>{props.label}</div>}
{props.children}
</div>
);
};
If you are using a function-as-a-child render prop:
export interface Props {
children: (foo: string) => React.ReactNode;
}
Something to add? File an issue.
Use type guards!
// Button props
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
href?: undefined;
};
// Anchor props
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
href?: string;
};
// Input/output options
type Overload = {
(props: ButtonProps): JSX.Element;
(props: AnchorProps): JSX.Element;
};
// Guard to check if href exists in props
const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps =>
"href" in props;
// Component
const Button: Overload = (props: ButtonProps | AnchorProps) => {
// anchor render
if (hasHref(props)) return <a {...props} />;
// button render
return <button {...props} />;
};
// Usage
function App() {
return (
<>
{/* 😎 All good */}
<Button target="_blank" href="https://www.google.com">
Test
</Button>
{/* 😭 Error, `disabled` doesnt exist on anchor element */}
<Button disabled href="x">
Test
</Button>
</>
);
}
View in the TypeScript Playground
ElementType
is pretty useful to cover most types that can be passed to createElement e.g.
function PassThrough(props: { as: React.ElementType<any> }) {
const { as: Component } = props;
return <Component />;
}
Thanks @eps1lon for this
Just as you can make generic functions and classes in TypeScript, you can also make generic components to take advantage of the type system for reusable type safety. Both Props and State can take advantage of the same generic types, although it probably makes more sense for Props than for State. You can then use the generic type to annotate types of any variables defined inside your function / class scope.
interface Props<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: Props<T>) {
const { items, renderItem } = props;
const [state, setState] = React.useState<T[]>([]); // You can use type T in List function scope.
return (
<div>
{items.map(renderItem)}
<button onClick={() => setState(items)}>Clone</button>
{JSON.stringify(state, null, 2)}
</div>
);
}
You can then use the generic components and get nice type safety through type inference:
ReactDOM.render(
<List
items={["a", "b"]} // type of 'string' inferred
renderItem={item => (
<li key={item}>
{item.toPrecision(3)} // Error: Property 'toPrecision' does not exist on
type 'string'.
</li>
)}
/>,
document.body
);
As of TS 2.9, you can also supply the type parameter in your JSX to opt out of type inference:
ReactDOM.render(
<List<number>
items={["a", "b"]} // Error: Type 'string' is not assignable to type 'number'.
renderItem={item => <li key={item}>{item.toPrecision(3)}</li>}
/>,
document.body
);
You can also use Generics using fat arrow function style:
interface Props<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
// Note the <T extends unknown> before the function definition.
// You can't use just `<T>` as it will confuse the TSX parser whether it's a JSX tag or a Generic Declaration.
// You can also use <T,> https://github.com/microsoft/TypeScript/issues/15713#issuecomment-499474386
const List = <T extends unknown>(props: Props<T>) => {
const { items, renderItem } = props;
const [state, setState] = React.useState<T[]>([]); // You can use type T in List function scope.
return (
<div>
{items.map(renderItem)}
<button onClick={() => setState(items)}>Clone</button>
{JSON.stringify(state, null, 2)}
</div>
);
};
The same for using classes: (Credit: Karol Majewski's gist)
interface Props<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
interface State<T> {
items: T[];
}
class List<T> extends React.PureComponent<Props<T>, State<T>> {
// You can use type T inside List class.
state: Readonly<State<T>> = {
items: []
};
render() {
const { items, renderItem } = this.props;
// You can use type T inside List class.
const clone: T[] = items.slice(0);
return (
<div>
{items.map(renderItem)}
<button onClick={() => this.setState({ items: clone })}>Clone</button>
{JSON.stringify(this.state, null, 2)}
</div>
);
}
}
Though you can't use Generic Type Parameters for Static Members:
class List<T> extends React.PureComponent<Props<T>, State<T>> {
// Static members cannot reference class type parameters.ts(2302)
static getDerivedStateFromProps(props: Props<T>, state: State<T>) {
return { items: props.items };
}
}
To fix this you need to convert your static function to a type inferred function:
class List<T> extends React.PureComponent<Props<T>, State<T>> {
static getDerivedStateFromProps<T>(props: Props<T>, state: State<T>) {
return { items: props.items };
}
}
children
is usually not defined as a part of the props type. Unless children
are explicitly defined as a part of the props
type, an attempt to use props.children
in JSX or in the function body will fail:
interface WrapperProps<T> {
item: T;
renderItem: (item: T) => React.ReactNode;
}
/* Property 'children' does not exist on type 'WrapperProps<T>'. */
const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
return (
<div>
{props.renderItem(props.item)}
{props.children}
</div>
);
};
/*
Type '{ children: string; item: string; renderItem: (item: string) => string; }' is not assignable to type 'IntrinsicAttributes & WrapperProps<string>'.
Property 'children' does not exist on type 'IntrinsicAttributes & WrapperProps<string>'.
*/
const wrapper = (
<Wrapper item="test" renderItem={item => item}>
{test}
</Wrapper>
);
View in the TypeScript Playground
To work around that, either add children
to the WrapperProps
definition (possibly narrowing down its type, as needed):
interface WrapperProps<T> {
item: T;
renderItem: (item: T) => React.ReactNode;
children: string; // The component will only accept a single string child
}
const Wrapper = <T extends {}>(props: WrapperProps<T>) => {
return (
<div>
{props.renderItem(props.item)}
{props.children}
</div>
);
};
or wrap the type of the props in React.PropsWithChildren
(this is what React.FC<>
does):
interface WrapperProps<T> {
item: T;
renderItem: (item: T) => React.ReactNode;
}
const Wrapper = <T extends {}>(
props: React.PropsWithChildren<WrapperProps<T>>
) => {
return (
<div>
{props.renderItem(props.item)}
{props.children}
</div>
);
};
Components, and JSX in general, are analogous to functions. When a component can render differently based on their props, it's similar to how a function can be overloaded to have multiple call signatures. In the same way, you can overload a function component's call signature to list all of its different "versions".
A very common use case for this is to render something as either a button or an anchor, based on if it receives a href
attribute.
type ButtonProps = JSX.IntrinsicElements["button"];
type AnchorProps = JSX.IntrinsicElements["a"];
// optionally use a custom type guard
function isPropsForAnchorElement(
props: ButtonProps | AnchorProps
): props is AnchorProps {
return "href" in props;
}
function Clickable(props: ButtonProps | AnchorProps) {
if (isPropsForAnchorElement(props)) {
return <a {...props} />;
} else {
return <button {...props} />;
}
}
They don't even need to be completely different props, as long as they have at least one difference in properties:
type LinkProps = Omit<JSX.IntrinsicElements["a"], "href"> & { to?: string };
function RouterLink(props: LinkProps | AnchorProps) {
if ("to" in props) {
return <a {...props} />;
} else {
return <Link {...props} />;
}
}
Approach: Generic Components
Here is an example solution, see the further discussion for other solutions. thanks to @jpavon
interface LinkProps {}
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;
type RouterLinkProps = Omit<NavLinkProps, "href">;
const Link = <T extends {}>(
props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
) => {
if ((props as RouterLinkProps).to) {
return <NavLink {...(props as RouterLinkProps)} />;
} else {
return <a {...(props as AnchorProps)} />;
}
};
<Link<RouterLinkProps> to="/">My link</Link>; // ok
<Link<AnchorProps> href="/">My link</Link>; // ok
<Link<RouterLinkProps> to="/" href="/">
My link
</Link>; // error
Approach: Composition
If you want to conditionally render a component, sometimes is better to use React's composition model to have simpler components and better to understand typings:
type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
type RouterLinkProps = Omit<AnchorProps, 'href'>
interface Button {
as: React.ComponentClass | 'a'
}
const Button: React.FunctionComponent<Button> = (props) => {
const {as: Component, children, ...rest} = props
return (
<Component className="button" {...rest}>{children}</Component>
)
}
const AnchorButton: React.FunctionComponent<AnchorProps> = (props) => (
<Button as="a" {...props} />
)
const LinkButton: React.FunctionComponent<RouterLinkProps> = (props) => (
<Button as={NavLink} {...props} />
)
<LinkButton to="/login">Login</LinkButton>
<AnchorButton href="/login">Login</AnchorButton>
<AnchorButton href="/login" to="/test">Login</AnchorButton> // Error: Property 'to' does not exist on type...
You may also want to use Discriminated Unions, please check out Expressive React Component APIs with Discriminated Unions.
Here is a brief intuition for Discriminated Union Types:
type UserTextEvent = { value: string; target: HTMLInputElement };
type UserMouseEvent = { value: [number, number]; target: HTMLElement };
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
if (typeof event.value === "string") {
event.value; // string
event.target; // HTMLInputElement | HTMLElement (!!!!)
return;
}
event.value; // [number, number]
event.target; // HTMLInputElement | HTMLElement (!!!!)
}
Even though we have narrowed based on event.value
, the logic doesn't filter up and sideways to event.target
. This is because a union type UserTextEvent | UserMouseEvent
could be BOTH at once. So TypeScript needs a better hint. The solution is to use a literal type to tag each case of your union type:
type UserTextEvent = {
type: "TextEvent";
value: string;
target: HTMLInputElement;
};
type UserMouseEvent = {
type: "MouseEvent";
value: [number, number];
target: HTMLElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
if (event.type === "TextEvent") {
event.value; // string
event.target; // HTMLInputElement
return;
}
event.value; // [number, number]
event.target; // HTMLElement
}
To streamline this you may also combine this with the concept of User-Defined Type Guards:
function isString(a: unknown): a is string {
return typeof a === "string";
}
Read more about User-Defined Type Guards in the Handbook.
Use the in
keyword, function overloading, and union types to make components that take either one or another sets of props, but not both:
type Props1 = { foo: string };
type Props2 = { bar: string };
function MyComponent(props: Props1 | Props2) {
if ("foo" in props) {
// props.bar // error
return <div>{props.foo}</div>;
} else {
// props.foo // error
return <div>{props.bar}</div>;
}
}
const UsageComponent: React.FC = () => (
<div>
<MyComponent foo="foo" />
<MyComponent bar="bar" />
{/* <MyComponent foo="foo" bar="bar"/> // invalid */}
</div>
);
View in the TypeScript Playground
Further reading: how to ban passing {}
if you have a NoFields
type.
type OneOrAnother<T1, T2> =
| (T1 & { [K in keyof T2]?: undefined })
| (T2 & { [K in keyof T1]?: undefined });
type Props = OneOrAnother<{ a: string; b: string }, {}>;
const a: Props = { a: "a" }; // error
const b: Props = { b: "b" }; // error
const ab: Props = { a: "a", b: "b" }; // ok
Thanks diegohaz
Say you want a Text component that gets truncated if truncate
prop is passed but expands to show the full text when expanded
prop is passed (e.g. when the user clicks the text).
You want to allow expanded
to be passed only if truncate
is also passed, because there is no use for expanded
if the text is not truncated.
You can do this by function overloads:
type CommonProps = {
children: React.ReactNode;
miscProps?: any;
};
type NoTruncateProps = CommonProps & { truncate?: false };
type TruncateProps = CommonProps & { truncate: true; expanded?: boolean };
// Function overloads to accept both prop types NoTruncateProps & TruncateProps
function Text(props: NoTruncateProps): JSX.Element;
function Text(props: TruncateProps): JSX.Element;
function Text(props: CommonProps & { truncate?: boolean; expanded?: boolean }) {
const { children, truncate, expanded, ...otherProps } = props;
const classNames = truncate ? ".truncate" : "";
return (
<div className={classNames} aria-expanded={!!expanded} {...otherProps}>
{children}
</div>
);
}
Using the Text component:
const App: React.FC = () => (
<>
{/* these all typecheck */}
<Text>not truncated</Text>
<Text truncate>truncated</Text>
<Text truncate expanded>
truncate-able but expanded
</Text>
{/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type '{ truncate: true; expanded?: boolean | undefined; }'. */}
<Text expanded>truncate-able but expanded</Text>
</>
);
Note: Omit was added as a first class utility in TS 3.5! 🎉
Sometimes when intersecting types, we want to define our own version of an attribute. For example, I want my component to have a label
, but the type I am intersecting with also has a label
attribute. Here's how to extract that out:
export interface Props {
label: React.ReactNode; // this will conflict with the InputElement's label
}
// this comes inbuilt with TS 3.5
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// usage
export const Checkbox = (
props: Props & Omit<React.HTMLProps<HTMLInputElement>, "label">
) => {
const { label } = props;
return (
<div className="Checkbox">
<label className="Checkbox-label">
<input type="checkbox" {...props} />
</label>
<span>{label}</span>
</div>
);
};
When your component defines multiple props, chances of those conflicts increase. However you can explicitly state that all your fields should be removed from the underlying component using the keyof
operator:
export interface Props {
label: React.ReactNode; // conflicts with the InputElement's label
onChange: (text: string) => void; // conflicts with InputElement's onChange
}
export const Textbox = (
props: Props & Omit<React.HTMLProps<HTMLInputElement>, keyof Props>
) => {
// implement Textbox component ...
};
As you can see from the Omit example above, you can write significant logic in your types as well. type-zoo is a nice toolkit of operators you may wish to check out (includes Omit), as well as utility-types (especially for those migrating from Flow).
(Contributed by @ferdaber)
There are a lot of places where you want to reuse some slices of props because of prop drilling, so you can either export the props type as part of the module or extract them (either way works).
The advantage of extracting the prop types is that you won't need to export everything, and a refactor of the source of truth component will propagate to all consuming components.
import { ComponentProps, JSXElementConstructor } from "react";
// goes one step further and resolves with propTypes and defaultProps properties
type ApparentComponentProps<
C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = C extends JSXElementConstructor<infer P>
? JSX.LibraryManagedAttributes<C, P>
: ComponentProps<C>;
You can also use them to strongly type custom event handlers if they're not written at the call sites themselves (i.e. inlined with the JSX attribute):
// my-inner-component.tsx
export function MyInnerComponent(props: {
onSomeEvent(
event: ComplexEventObj,
moreArgs: ComplexArgs
): SomeWeirdReturnType;
}) {
/* ... */
}
// my-consuming-component.tsx
export function MyConsumingComponent() {
// event and moreArgs are contextually typed along with the return value
const theHandler: Props<typeof MyInnerComponent>["onSomeEvent"] = (
event,
moreArgs
) => {};
return <MyInnerComponent onSomeEvent={theHandler} />;
}
You can provide good information when bad things happen.
class InvalidDateFormatError extends RangeError {}
class DateIsInFutureError extends RangeError {}
/**
* // optional docblock
* @throws {InvalidDateFormatError} The user entered date incorrectly
* @throws {DateIsInFutureError} The user entered date in future
*
*/
function parse(date: string) {
if (!isValid(date))
throw new InvalidDateFormatError("not a valid date format");
if (isInFuture(date)) throw new DateIsInFutureError("date is in the future");
// ...
}
try {
// call parse(date) somewhere
} catch (e) {
if (e instanceof InvalidDateFormatError) {
console.error("invalid date format", e);
} else if (e instanceof DateIsInFutureError) {
console.warn("date is in future", e);
} else {
throw e;
}
}
Simply throwing an exception is fine, however it would be nice to make TypeScript remind the consumer of your code to handle your exception. We can do that just by returning instead of throwing:
function parse(
date: string
): Date | InvalidDateFormatError | DateIsInFutureError {
if (!isValid(date))
return new InvalidDateFormatError("not a valid date format");
if (isInFuture(date)) return new DateIsInFutureError("date is in the future");
// ...
}
// now consumer *has* to handle the errors
let result = parse("mydate");
if (result instanceof InvalidDateFormatError) {
console.error("invalid date format", result.message);
} else if (result instanceof DateIsInFutureError) {
console.warn("date is in future", result.message);
} else {
/// use result safely
}
// alternately you can just handle all errors
if (result instanceof Error) {
console.error("error", result);
} else {
/// use result safely
}
You can also describe exceptions with special-purpose data types (don't say monads...) like the Try
, Option
(or Maybe
), and Either
data types:
interface Option<T> {
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Option<U>): Option<U>
getOrElse(value: T): T
}
class Some<T> implements Option<T> {
constructor(private value: T) {}
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Some<U>): Some<U>
flatMap<U>(f: (value: T) => Option<U>): Option<U> {
return f(this.value)
}
getOrElse(): T {
return this.value
}
}
class None implements Option<never> {
flatMap<U>(): None {
return this
}
getOrElse<U>(value: U): U {
return value
}
}
// now you can use it like:
let result = Option(6) // Some<number>
.flatMap(n => Option(n*3)) // Some<number>
.flatMap(n = new None) // None
.getOrElse(7)
// or:
let result = ask() // Option<string>
.flatMap(parse) // Option<Date>
.flatMap(d => new Some(d.toISOString()) // Option<string>
.getOrElse('error parsing string')
Sometimes DefinitelyTyped can get it wrong, or isn't quite addressing your use case. You can declare your own file with the same interface name. Typescript will merge interfaces with the same name.
TypeScript Versions often introduce new ways to do things; this section helps current users of React + TypeScript upgrade TypeScript versions and explore patterns commonly used by TypeScript + React apps and libraries. This may have duplications with other sections; if you spot any discrepancies, file an issue!
TypeScript version guides before 2.9 are unwritten, please feel free to send a PR! Apart from official TS team communication we also recommend Marius Schulz's blog for version notes. For more TypeScript history, see A Brief History of TypeScript Types and A Brief History of DefinitelyTyped
- Type arguments for tagged template strings (e.g.
styled-components
):
export interface InputFormProps {
foo: string; // this is understood inside the template string below
}
export const InputForm = styledInput<InputFormProps>`
color:
${({ themeName }) => (themeName === "dark" ? "black" : "white")};
border-color: ${({ foo }) => (foo ? "red" : "black")};
`;
- JSX Generics
Helps with typing/using generic components:
// instead of
<Formik render={(props: FormikProps<Values>) => ....}/>
// usage
<Formik<Values> render={props => ...}/>
<MyComponent<number> data={12} />
- Typed rest parameters for writing arguments of variable length:
// `rest` accepts any number of strings - even none!
function foo(...rest: string[]) {
// ...
}
foo("hello"); // works
foo("hello", "world"); // also works
- Support for
propTypes
andstatic defaultProps
in JSX usingLibraryManagedAttributes
:
export interface Props {
name: string;
}
export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
}
static defaultProps = { name: "world" };
}
// Type-checks! No type assertions needed!
let el = <Greet />;
- new
Unknown
type
For typing API's to force type checks - not specifically React related, however very handy for dealing with API responses:
interface IComment {
date: Date;
message: string;
}
interface IDataService1 {
getData(): any;
}
let service1: IDataService1;
const response = service1.getData();
response.a.b.c.d; // RUNTIME ERROR
// ----- compare with -------
interface IDataService2 {
getData(): unknown; // ooo
}
let service2: IDataService2;
const response2 = service2.getData();
// response2.a.b.c.d; // COMPILE TIME ERROR if you do this
if (typeof response === "string") {
console.log(response.toUpperCase()); // `response` now has type 'string'
}
You can also assert a type, or use a type guard against an unknown
type. This is better than resorting to any
.
- Project References
Project references allow TypeScript projects to depend on other TypeScript projects – specifically, allowing tsconfig.json files to reference other tsconfig.json files. This lets large codebases scale without recompiling every part of the codebase every time, by breaking it up into multiple projects.
In each folder, create a tsconfig.json that includes at least:
{
"compilerOptions": {
"composite": true, // tells TSC it is a subproject of a larger project
"declaration": true, // emit .d.ts declaration files since project references dont have access to source ts files. important for project references to work!
"declarationMap": true, // sourcemaps for .d.ts
"rootDir": "." // specify compile it relative to root project at .
},
"include": [
"./**/*.ts
],
"references": [ // (optional) array of subprojects your subproject depends on
{
"path": "../myreferencedproject", // must have tsconfig.json
"prepend": true // concatenate js and sourcemaps generated by this subproject, if and only if using outFile
}
]
}
and the root tsconfig.json
that references top level subproject:
{
"files: [],
"references": [
{"path": "./proj1"},
{"path": "./proj2"},
]
}
and you must run tsc --build
or tsc -b
.
To save the tsconfig boilerplate, you can use the extends
option:
{
"extends": "../tsconfig.base",
// more stuff here
}
- Properties declarations on functions
Attaching properties to functions like this "just works" now:
export const FooComponent = ({ name }) => <div>Hello! I am {name}</div>;
FooComponent.defaultProps = {
name: "swyx"
};
nothing specifically React related.
nothing specifically React related.
export function useLoading() {
const [isLoading, setState] = React.useState(false);
const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};
return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]
}
More info on places you can use const assertions.
-
Built-in
<Omit>
Type!! -
Higher order type inference from generic constructors
type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
props: P;
constructor(props: P);
}
declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;
type NestedProps<T> = { foo: number; stuff: T };
declare class GenericComponent<T> extends Component<NestedProps<T>> {}
// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);
See also Notes from Google upgrading to 3.5
Nothing particularly React specific but the playground got an upgrade and Ambient Classes and Functions Can Merge
- Optional Chaining
let x = foo?.bar.baz();
// is equivalent to
let x = foo === null || foo === undefined ? undefined : foo.bar.baz();
// Optional Element access
function tryGetFirstElement<T>(arr?: T[]) {
return arr?.[0];
}
// Optional Call
async function makeRequest(url: string, log?: (msg: string) => void) {
log?.(`Request started at ${new Date().toISOString()}`);
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;
}
- Nullish Coalescing
let x = foo ?? bar();
// equivalent to
let x = foo !== null && foo !== undefined ? foo : bar();
YOU SHOULD USUALLY USE ??
WHEREVER YOU NORMALLY USE ||
unless you truly mean falsiness:
function ShowNumber({ value }: { value: number }) {
let _value = value || 0.5; // will replace 0 with 0.5 even if user really means 0
// etc...
}
- Assertion Functions
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg);
}
}
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
You can also assert without a custom function:
function assertIsString(val: any): asserts val is string {
if (typeof val !== "string") {
throw new AssertionError("Not a string!");
}
}
function yell(str: any) {
assertIsString(str);
// Now TypeScript knows that 'str' is a 'string'.
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
ts-nocheck
You can now add // @ts-nocheck
to the top of TypeScript files! good for migrations.
https://github.com/Microsoft/TypeScript/wiki/Roadmap
Did you also know you can read the TypeScript spec online?? https://github.com/microsoft/TypeScript/blob/master/doc/spec.md
Sometimes writing React isn't just about React. While we don't focus on other libraries like Redux (see below for more on that), here are some tips on other common concerns when making apps with React + TypeScript.
propTypes
may seem unnecessary with TypeScript, especially when building React + TypeScript apps, but they are still relevant when writing libraries which may be used by developers working in Javascript.
interface IMyComponentProps {
autoHeight: boolean;
secondProp: number;
}
export class MyComponent extends React.Component<IMyComponentProps, {}> {
static propTypes = {
autoHeight: PropTypes.bool,
secondProp: PropTypes.number.isRequired
};
}
Something to add? File an issue.
Typescript uses TSDoc, a variant of JSDoc for Typescript. This is very handy for writing component libraries and having useful descriptions pop up in autocomplete and other tooling (like the Docz PropsTable). The main thing to remember is to use /** YOUR_COMMENT_HERE */
syntax in the line just above whatever you're annotating.
import React from "react";
interface MyProps {
/** Description of prop "label".
* @default foobar
* */
label?: string;
}
/**
* General component description in JSDoc format. Markdown is *supported*.
*/
export default function MyComponent({ label = "foobar" }: MyProps) {
return <div>Hello world {label}</div>;
}
View in the TypeScript Playground
Something to add? File an issue.
Often when creating similar components or components that have a parent-child relationship, it is useful to namespace your components. Types can easily be added be using Object.assign()
;
import React from "react";
const Input = (props: any) => <input {...props} />;
const Form = React.forwardRef<HTMLDivElement, any>(
({ children, ...otherProps }, ref) => (
<form {...otherProps} ref={ref}>
{children}
</form>
)
);
/**
* Exported components now can be used as `<Form>` and `<Form.Input>`
*/
export default Object.assign(Form, { Input: Input });
View in the TypeScript Playground
(Contributed by @bryceosterhaus, see further discussion)
Something to add? File an issue.
I do like Docz which takes basically 1 line of config to accept Typescript. However it is newer and has a few more rough edges (many breaking changes since it is still < v1.0)
For developing with Storybook, read the docs I wrote over here: https://storybook.js.org/configurations/typescript-config/. This includes automatic proptype documentation generation, which is awesome :)
Something to add? File an issue.
You should check out large projects that are migrating from flow to pick up concerns and tips:
Useful libraries:
- https://github.com/bcherny/flow-to-typescript
- https://github.com/Khan/flow-to-ts
- https://github.com/piotrwitek/utility-types
If you have specific advice in this area, please file a PR!
Something to add? File an issue.
There isn't any real secret to Prettier for TypeScript. But its a great idea to run prettier on every commit!
yarn add -D prettier husky lint-staged
// inside package.json
{
//...
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"src/*.{ts,tsx,js,jsx,css,scss,md}": [
"prettier --trailing-comma es5 --single-quote --write",
"git add"
],
"ignore": [
"**/dist/*, **/node_modules/*"
]
}
},
"prettier": {
"printWidth": 80,
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
}
This is set up for you in tsdx.
Yes, you can test your types! You shouldn't use it for EVERYTHING, but it can help prevent regressions:
- https://github.com/azz/jest-runner-tsc
- https://github.com/SamVerschueren/tsd
- https://github.com/ikatyang/dts-jest (Demo)
- https://github.com/microsoft/dtslint (Intro to dtslint)
Lets say you want to use de-indent
, but it isn't typed or on DefinitelyTyped. You get an error like this:
[ts]
Could not find a declaration file for module 'de-indent'. '/Users/swyx/Work/react-sfc-loader/node_modules/de-indent/index.js' implicitly has an 'any' type.
Try `npm install @types/de-indent` if it exists or add a new declaration (.d.ts) file containing `declare module 'de-indent';` [7016]
So create a .d.ts
file anywhere in your project with the module definition:
// de-indent.d.ts
declare module "de-indent" {
function deindent(): void;
export = deindent; // default export
}
Further Discussion
Any other tips? Please contribute on this topic! We have an ongoing issue here with some references. We have more discussion and examples in our issue here.
The @types
typings export both "public" types meant for your use as well as "private" types that are for internal use.
Check SaltyCrane's React TypeScript Cheatsheet for a nice autogenerated complete reference.
Namespace: React
Most Commonly Used Interfaces and Types
ReactNode
- anything that is renderable inside of JSX, this is NOT the same as what can be rendered by a component!Component
- base class of all class-based componentsPureComponent
- base class for all class-based optimized componentsFC
,FunctionComponent
- a complete interface for function components, often used to type external components instead of typing your ownCSSProperties
- used to type style objects- all events: used to type event handlers
- all event handlers: used to type event handlers
- all consts:
Children
,Fragment
, ... are all public and reflect the React runtime namespace
Not Commonly Used but Good to know
Ref
- used to typeinnerRef
ElementType
- used for higher order components or operations on componentsComponentType
- used for higher order components where you don't specifically deal with the intrinsic componentsReactPortal
- used if you specifically need to type a prop as a portal, otherwise it is part ofReactNode
ComponentClass
- a complete interface for the produced constructor function of a class declaration that extendsComponent
, often used to type external components instead of typing your ownJSXElementConstructor
- anything that TypeScript considers to be a valid thing that can go into the opening tag of a JSX expressionComponentProps
- props of a componentComponentPropsWithRef
- props of a component where if it is a class-based component it will replace theref
prop with its own instance typeComponentPropsWithoutRef
- props of a component without itsref
prop- all methods:
createElement
,cloneElement
, ... are all public and reflect the React runtime API
@Ferdaber's note: I discourage the use of most ...Element
types because of how black-boxy JSX.Element
is. You should almost always assume that anything produced by React.createElement
is the base type React.ReactElement
.
Namespace: JSX
Element
- the type of any JSX expressionLibraryManagedAttributes
- It specifies other places where JSX elements can declare and initialize property types. Used to resolve staticdefaultProps
andpropTypes
with the internal props type of a component.IntrinsicElements
- every possible built-in component that can be typed in as a lowercase tag name in JSX
Not commonly used but good to know
IntrinsicAttributes
set of attributes that allIntrinsicElements
support... basically justkey
.ElementChildrenAttribute
name of property that TS looks at to figure out what types of children a component supports. Basically thechildren
propertyElementAttributesProperty
name of property that TS looks at to figure out what attributes a component supports. Basically theprops
property (for a class instance)
Don't use/Internal/Deprecated
Anything not listed above is considered an internal type and not public. If you're not sure you can check out the source of @types/react
. The types are annotated accordingly.
SFCElement
SFC
ComponentState
LegacyRef
StatelessComponent
ReactType
The attributes allowed on host components such as button
or img
follow the
HTML living standard. New features that are not yet part of specification
or are only implemented by certain browsers will therefore cause a type error. If
you specifically write code for these browsers or polyfill this attributes you can
use module augmentation to still get those components type checked without having
to use any
or @ts-ignore
.
In this example we'll add the loading
attribute which adds support for lazy-loading images on Chrome:
// react-unstable-attributes.d.ts
import "react";
declare module "react" {
interface ImgHTMLAttributes<T> extends HTMLAttributes<T> {
loading?: "auto" | "eager" | "lazy";
}
}
To be written