Back to Blog

Generics in Typescript & React

5 min read


January 06, 2022 (Updated on January 10, 2022)

Generics in Typescript & React



Generics can be a difficult concept for some, but when used correctly, can improve re-usability and type safety within your applications.


You can think of generics as a placeholder type, that can be specific to your implenentation when you use it. Unlike any and unknown, generics provide flexibility without sacrificing on type safety.

Here, we'll use an example function called split. This function takes an array, and splits it's content into two separate arrays and returns them.

export const split = (items: any[]): [any[], any[]] => {
    const setOne = [];
    const setTwo = [];, index) => {
        if (index % 2 == 0) {
        } else {

    return [setOne, setTwo];

While we could use any here, we would lose out on type safety when interacting with the returning data. Instead, we should make use of generics to indicate the types are determined on what is being passed into the function. For example, we would adjust our code as such:

export const split = <T extends any>(items: T[]): [T[], T[]] => {
    const setOne: T[] = [];
    const setTwo: T[] = [];, index) => {
        if (index % 2 == 0) {
        } else {

    return [setOne, setTwo];

With this code, passing a string[] will return a [string[], string[]]; the types are inferred from the data being passed in. Generic parameters don't have to be called T, although convention is to start the parameter name with T, such as TData.

Constrainted types

Generic types can be constrained to only allow a subset of a type to use the function. This can be useful if you want to perform actions on a property of an object, but don't know what the implemented object will look like.

type MyType = {
    id: number;

type InheritedType = MyType & {
    name: string;
    birthday: Date;

export const split = <T extends MyType>(items: T[]): [T[], T[]] => {
    const setOne: T[] = [];
    const setTwo: T[] = [];, index) => {
        // We can safely use here, since it's guaranteed to exist from the constraint.

        if (index % 2 == 0) {
        } else {

    return [setOne, setTwo];

Objects of MyType, InheritedType and any type with an id: number property would be valid to pass into this function, thanks to the constraint.

Inferred variables

Some generic functions require the developer to be explicit in what types to use. Others are able to infer automatically, based on the data being passed in.

For instance, the following hook doesn't have a variable to infer types from, so the type is required when the hook is used:

export const useDistanceFromTop = <TElement extends HTMLElement>(): [
    { distance: number }
] => {
    const ref = useRef<TElement>();
    const distance = useIfClient(
        () => (ref.current?.getBoundingClientRect().top ?? 0) + (document?.documentElement.scrollTop ?? 0),

    return [ref, { distance }];

Automatic and manual type inference in the same function doesn't currently exist, so even if you had a variable that could be inferred, if you're having to define one type, you need to define them all.

Generics in Components

Generics can also be used within React components, although their functionality is limited as generic types don't pass down through the Context API. One such use-case might be using a function as a child, as shown below:

type Props<T> = {
    items: T[]
    children: (item: T) => JSX.Element

export function ExampleComponent<T>({ items, children }: Props<T>) {
    return <div>
        { => children(i))}

//To use like...
items = [
    { id: 1 },
    { id: 2 },

<ExampleComponent items={items}>{(item) => <div>{}</div></ExampleComponent>

This would allow you to create a component with re-usable functionality (such as a checkbox list), but customise the content you're rendering.

Generics in Hooks

Hooks can also make use of generics to provide re-usable functionality alongside React lifecycle events. Some examples can be found in my code snippets blog post, with the below examples pulled from it:

type UseRelativeScrollPercentageResult<TStartElement, TEndElement> = [
    { percentage: number }

export const useRelativeScrollPercentage = <
    TStartElement extends HTMLElement = HTMLElement,
    TEndElement extends HTMLElement = HTMLElement
    offset = 0
): UseRelativeScrollPercentageResult<TStartElement, TEndElement> => {
    const [fromRef, { distance: fromDistance }] = useDistanceFromTop<TStartElement>();
    const [toRef, { distance: toDistance }] = useDistanceFromTop<TEndElement>();
    const { y } = useWindowScroll();

    const current = y - fromDistance + offset;
    const end = toDistance - fromDistance;

    const percentage = useMemo(() => {
        return (current / end) * 100;
    }, [current, end, y]);

    return [fromRef, toRef, { percentage }];

The above example uses constrained generic types to restrict the types to HTMLElements, allowing it to be used with any HTML tag while maintaining the correct ref type.

export const useFilter = <TFilter, TData>(
    data: TData[],
    filter: TFilter = undefined,
    predicate: (data: TData, filter: TFilter) => boolean
): TData[] => {
    return useMemo(() => {
        if (!filter || (Array.isArray(filter) && filter.length === 0)) {
            return data;

        return data.filter((entry) => predicate(entry, filter));
    }, [filter, data, predicate]);

This example infers the TData and TFilter types automatically from the parameters passed into it, and applies those inferred types to the return type.


I hope after reading this article, generics are clearer to use in how they are built, and the functionality they can provide. As always, send any questions you have to the Q&A page and I'll get in touch.

Discuss on Twitter
Share on Twitter

Written by Adam Young

Adam Young is a front-end engineer, who specializes in React and modern web technologies. He's working at as a front-end engineer. He currently lives in the the town of Darlington, with his two cats.

Related Articles

Stay up to date

Get the latest articles on web development, technology and best practices, straight to your inbox.




All rights reserved Β© Adam Young 2022

Source code on Github