7 TypeScript Utility Types For React Developers
June 06, 2022 - 6 minutes
It’s almost unthinkable to do React development without TypeScript. The two technologies have been established to be an extremely powerful duo, enabling developers to create high-quality and maintainable React applications at scale. TypeScript plays an integral part in this process.
However, being a good React developer doesn’t automatically translate into being a good TypeScript developer. Writing quality TypeScript code is a separate skill, just like writing quality React code. This is even more prominent when combining the two skills together.
To help with this, this article goes over 7 different TypeScript utility types. Based on personal experience, these utility types are extremely helpful to React developers on a daily basis. They’re applicable in common React development practises or geared towards React development. Mastering them will significantly improve your qualities as a TypeScript React developer.
Pick
The Pick
utility type allows you to construct a new object type based on an existing object type or interface. It accepts two arguments, the existing object type or interface and a string literal or union of string literals of keys that should be included in the resulting object type.
For React developers, this is especially useful when a component’s props share typings with another component or data type. Instead of specifying all the types manually in the new type, you can use the Pick
utility. This also creates an explicit connection between the types and automatically aligns the types across all the related typings.
import { PropsWithChildren } from "react";
import { SomeOtherComponent, SomeOtherComponentProps } from "./someOtherComponent";
type ComponentProps = Pick<Something, "propA" | "propB" | "children"> & {
wrapperClassName?: string;
}
export const Component = (props: ComponentProps) => (
<div className={props.wrapperClassName}>
<SomeOtherComponent propA={props.propA} propB={props.propB}>
{props.children}
</SomeOtherComponent>
</div>
);
Omit
The Omit
utility type is very similar to the previously discussed Pick
utility type but basically does the inverse. Instead of specifying with keys to include in the constructed object type or interface, you specify the ones that should be excluded. This becomes useful when you want to reuse a large part of an existing object type or only want to exclude a small number of them.
In terms of use cases in React development, they’re similar to the use cases for Pick
. Think of taking over types from another component’s props or data type. However, the minor difference is that Omit
is more explicit about the entries that should be excluded rather than included.
import { PropsWithChildren } from "react";
import { SomeOtherComponent, SomeOtherComponentProps } from "./someOtherComponent";
type ComponentProps = Omit<Something, "propC" | "propD"> & {
wrapperClassName?: string;
}
export const Component = (props: ComponentProps) => (
<div className={props.wrapperClassName}>
<SomeOtherComponent propA={props.propA} propB={props.propB}>
{props.children}
</SomeOtherComponent>
</div>
);
Partial
The Partial
utility type allows you to construct a new object type where all the properties in the original object type or interface are set to optional. Basically, it creates a subset of typings compared to the original one.
For React development, this can be especially useful inside of tests or utility functions that provide props for components. In those scenarios, it’s often unwanted to provide all the props at once. Different parts of the props might come from different sources or functions, while all of them together complete the set of props.
import { mount } from "enzyme";
import { SomeComponent, SomeComponentProps } from "./someComponent";
function mountComponent(props: Partial<SomeComponentProps>): ReactWrapper {
return mount(
<SomeComponent variant="normal" title="Test Title" {...props} />
);
}
describe("SomeComponent", () => {
test("works like it should", () => {
const onChangeMock = jest.fn();
const component = mountComponent({ onChange: onChangeMock });
});
});
NonNullable
Oftentimes in React, you’ll be dealing with values that can be optional or nullable. Think of optional props, potentially missing data, or filtering for missing values. In all of these cases, TypeScript will let you know that these variables can also be undefined
or null
.
But in certain scenarios, you want to specify towards TypeScript that it isn’t possible or that they can’t be undefined
or null
. Think of initialising a component’s prop that is marked as optional, but is definitely set in the component that uses it. In those cases, the NonNullable
utility type can be used to construct a new type without null
and undefined
.
type OtherComponentProps = {
// ...
onChange?: (value: number) => void;
};
// -----
const Component = () => {
// We know for sure it's not nullable as we're initialising it, but TypeScript doesn't so we have to help it.
const changeValue = useCallback<NonNullable<OtherComponentProps["onChange"]>>(
(value) => {
if (value > 0) {
doSomethingWithTheValue(value);
}
},
[]
);
return <SomeOtherComponent onChange={changeValue} />;
};
React.ComponentProps
Sometimes you don’t have access to the TypeScript typings of a component’s prop, but only the component itself. Think of a component from an external library or when it’s not desired to expose the component’s props typing to the outside world. While it’s understandable, it makes it difficult to reuse or extend upon existing typings. Implementing them manually would introduce redundancy and increase the maintenance burden.
Instead, you can use React’s ComponentProps
utility type to get access to the props’ typing of a component in those scenarios. As the name indicates, it accepts the type of a component as its arguments and will construct a new type with the types of the component’s props.
import { ComponentProps } from "react";
import { ExternalComponent } from "external-lib";
type InternalComponentProps = ComponentProps<typeof ExternalComponent> & {
outerClassName: string;
};
const InternalComponent = ({
outerClassName,
...restProps
}: InternalComponentProps) => (
<div className={outerClassName}>
<ExternalComponent {...restProps} />
</div>
);
React.MouseEventHandler
An integral part of React applications is interactivity for users. A common way of implementing this is done through event handlers, most prominently mouse events. The difficult part of typing these event handlers is the fact that there are multiple parts to it. There is the entire event handler itself, the event that is provided to the handler, and a potential return value.
When starting out with TypeScript in React, it’s tempting to type each of those parts individually. While that works as a short term solution, it’s not a solution that scales well into the future. Instead, you can make use of React’s utility type MouseEventHandler
. It’s made specifically for these use cases and accepts an optional argument for the targetted HTML element.
import { MouseEventHandler, PropsWithChildren } from "react";
type ComponentProps = {
title: string;
caption: string;
onClick: MouseEventHandler<HTMLButtonElement>;
};
const Component = (props: ComponentProps) => (
<div>
<h2>{props.title}</h2>
<button onClick={props.onClick}>{props.caption}</button>
</div>
);
React.PropsWithChildren
One of the greatest aspects to React is its composable nature which allows components to be nested inside of other components from the outside. Most of the time, this is done through the natural children
prop from React.
One of the ways to tell TypeScript that a component accepts the children
prop is by explicitly stating it in the props’ typing. Something along the lines of children: ReactNode
will look familiar to many React developers that have adopted TypeScript.
However, another way to implement this is through React’s utility type PropsWithChildren
. It automatically constructs a new type with the respective typing, with the addition of marking it as optional and nullable.
import { MouseEventHandler, PropsWithChildren } from "react";
type ComponentProps = {
title: string;
onClick: MouseEventHandler<HTMLButtonElement>;
};
// `props.children` exists because of `PropsWithChildren`.
const Component = (props: PropsWithChildren<ComponentProps>) => (
<div>
<h2>{props.title}</h2>
<button onClick={props.onClick}>{props.children}</button>
</div>
);
Final Thoughts
In modern React development, it’s almost unthinkable to not have TypeScript involved. It improves the quality and maintainability of the code tremendously. However, being able to use TypeScript properly is an entirely separate skill on its own. This especially applies to the combination of TypeScript and React.
To help with this, this article went over 7 different TypeScript utility types that are useful to React developers. These utilities are either useful in common React development practises or geared specifically towards React development. Understanding and mastering these types will significantly improve the quality of your typed React code.