Notes on TypeScript: React and Generics
Tags: react, typescript, types
Published: 2019-03-02 18:40:08

---
title:  Notes on TypeScript: React and Generics
published: true
description: React and Generics
tags: react, typescript, types
series: Notes on TypeScript
---

### Introduction

These notes should help in better understanding `TypeScript` and might be helpful when needing to lookup up how leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

### Generics

If you have been reading along the "Notes on TypeScript" series, then you will have seen extensive usage of generics so far. While we have been using generics, we haven't actually talked about generics and why they are useful. In this part of the series, we will first try to better understand the generics topics and then see how we can leverage generics when working with React and TypeScript.

When writing software, one aspect is that we want to be able to reuse some functionality without having to write a specific functionality for every possible input type. Let's take the following example as a starting point:

```js
function isDefinedNumber(a: number) : boolean {
  return a !== null || a !== undefined;
}

function isDefinedString(a: string) : boolean {
  return a!== null || a !== undefined;
}
```

We wouldn't write explicit functions for `string` or `number` inputs, rather we would write a function with the following signatures:

```js
function isDefined<Type>(a: Type) : boolean {
  return a!== null || a !== undefined;
}
```

`isDefined` expects an input of generic `Type`. TypeScript will try to infer the argument and assign the correct type. Let's continue with another example, where we want to infer the return type:

```js
function of<Type>(a: Type) : Type[] {
  return [a];
}

const toNumbers = of(1); // const toNumbers: number[]
const toStrings = of("Test Of"); // const toString: string[]
```

In the `of` example, we can see that we don't even need to define the type, as TypeScript can infer the argument type. This is not applicable in all cases, sometimes we have to be explicit about the type. We could also have defined the above functions like so:

```js
const toNumbers = of<number>(1); // const toNumbers: number[]
const toStrings = of<string>("Test Of"); // const toString: string[]
```

Technically we could have used `any`:

```js
function of(a: any) : any {
  if (a.length !== undefined) {
    return a
  }
  return a;
}
```

But there is a big difference between using `any` and generics. If you take a closer look at the above example, we don't know anything about the input argument. Calling `of` with an `undefined` or `null` value will result in an error. Generics can infer the exact type and enforce to handle the input accordingly inside the function body. The same example using generics:

```js
function of<Type>(a: Type) : Type[] {
  if (a.length !== undefined) { // error: Property 'length' does not exist on 'Type'
    return a
  }
  return [a];
}
```

We have to be more explicit when dealing with generics, the example can be rewritten to the following:

```js
function of<Type>(a: Type | Type[]) : Type[] {
  if (Array.isArray(a)) {
    return a
  }
  return [a];
}


const a = of(1); // const a: number[]
const b = of([1]); // const b: number[]
```

Using generics enables us to reuse functionality, as `a` is of type `Type` or an array of type `Type`. When passing in `1` as an argument, `Type` binds to `number`, the same happens when passing in `[1]`, `Type` binds to `number`.

While we have seen functions using generics, we can also use generics with classes, which might be interesting when writing class components in React.

```js
class GenericClass<Type> {
  of = (a: Type | Type[]): Type[] => {
    if (Array.isArray(a)) {
      return a;
    }
    return [a];
  };
}

const genericClass = new GenericClass<number>();
const a = genericClass.of(1); // const a: number[]
const b = genericClass.of("1"); // error!
const c = genericClass.of([1]); // const c: number[]
```

The examples we have seen so far should help us in understanding the basics, we will build on this knowledge when using generics with React components.

### React and Generics

When working with React we might have a function component where we need to infer the argument type.
We might be building a component that expects a number or string or an array of type number or string.

```js
type RowProps<Type> = {
  input: Type | Type[];
};

function Rows<Type>({input}: RowProps<Type>) {
  if (Array.isArray(input)) {
    return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
  }
  return <div>{input}</div>
}

// usage

<Rows input={[1]} />
<Rows input={1} />
<Rows input={true} /> // Also works!
```

This works, but it also works for any value right now. We can pass in `true` and TypeScript will not complain. We need to restrict `Type` by ensuring `Type` either extends `number` or `string`.

```js
function Rows<Type extends number | string>({input}: RowProps<Type>) {
  if (Array.isArray(input)) {
    return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
  }
  return <div>{input}</div>
}

<Rows input={[1]} />
<Rows input={1} />
<Rows input="1" />
<Rows input={["1"]} />
<Rows input={true} /> //Error!
```

We can ensure that only expected types can be provided now. It's also interesting to note that we can make our prop type definition generic, as seen in the above example:

```js
type RowProps<Type> = {
  input: Type | Type[];
};
```

Next, we will build a more advanced example to see why generics can help us build reusable React components. We will build a component that expects two different inputs. Based on these inputs we will calculate a third value and the pass in a flat object based on the original inputs as well as the newly calculated value to a provided render prop.

```js
type RenderPropType<InputType, OtherInputType> = { c: number } & InputType &
  OtherInputType;

type RowComponentPropTypes<InputType, OtherInputType> = {
  input: InputType;
  otherInput: OtherInputType;
  render: (props: RenderPropType<InputType, OtherInputType>) => JSX.Element;
};
```

The first step is to define the `RowComponentPropTypes`, where we let TypeScript infer the provided arguments, and based on the bind types define the `render` function via using `RenderPropType`. `RenderPropType` is an intersection of the new type `{c: number}`, which we will calculate, and `InputType` and `OtherInputType`. We have been making heavy use of generics so far.

We might not know the exact shape of the provided inputs, so our next step is to restrict the provided types on the component level.

```js
class RowComponent<
  InputType extends { a: number },
  OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
  // implementation...
}
```

By using `InputType extends { a: number }` we can ensure that our input has an `a` property of type `number` provided, the same for `OtherInputType`. Now we can implement the `RowComponent` that ensures we can provide `a, b, c` properties to a `render` function.

Finally, this is our complete example implementation:

```js
class RowComponent<
  InputType extends { a: number },
  OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
  convert = (input: InputType, output: OtherInputType) => {
    return { c: input.a + output.b, ...input, ...output };
  };
  render() {
    return this.props.render(
      this.convert(this.props.input, this.props.otherInput)
    );
  }
}

<RowComponent
  input={{ a: 1 }}
  otherInput={{ b: 2 }}
  render={({ a, b, c }) => (
    <div>
      {a} {b} {c}
    </div>
  )}
/>
```

We should have a basic understanding of generics and how to leverage them when working with React and TypeScript now.

If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)