Self-referencing types and generics: what are they and how can I use them in React?
Tags: webdev, react, typescript
Published: 2023-10-30 10:11:25

Hi everyone! So I have this React app that I am currently building, and I have a parent component which has a piece of state, and contains a child component which I want to be able to both receive and manage that state. It could be a controlled input component for a form, perhaps.

```typescript
export function Form() {
  const [username, setUsername] = useState('');
  return (
    <form>
      <ControlledInput value={username} setValue={setUsername} />
    </form>
  )
}
```

Basically the simplest and most useless form you will ever see, I'm sure you agree!

Now, if we take a look at the props interface for our `ControlledInput` we will see that nothing looks too out of the ordinary:

```typescript
interface ControlledInputProps {
  setValue: () => void;
  value: string;
}
```

Simple enough, right?

**Will this work?** Yes.

###### Okay, so what is the problem?

Well, this presents 2 potential problems, mainly surrounding accuracy and maintainability.

**Accuracy**: if we focus just on the `() => void` part for a second, we can see that it is not necessarily a state updater, but _any function that does not return anything_, so if I were to pass it even just `setValue={() => {console.log("This will work fine, honest!")}}`, it would happily accept that and not tell me that there is an issue.

###### So, how do we fix this?

Well, React allows us to import 2 types: `Dispatch` and `SetStateAction`, both of these seem fairly self-explanatory, and those who have used `useReducer` with TypeScript in the past will already be familiar with `Dispatch`. `SetStateAction` allows you to not have to worry about typing the action yourself, just pass it the type of the state you're updating as a generic so you'll finally end up with `Dispatch<SetStateAction<string>>`:

```typescript
interface ControlledInputProps {
  setValue: Dispatch<SetStateAction<string>>;
  value: string;
}
```

Immediately, even just to the human eye, is that not much more descriptive of what `setValue` does? This also limits us to only passing in functions that are state updaters for strings. This does mean, though, that you still have to make sure you are passing in the right piece of state with its setter! If you pass `value={username} setValue={setPassword}` then you will still have a bad time, and as of the time of writing this I am not too sure of a way around that. `value={username} setValue={setAge}`, on the other hand? You'll get those warnings.

###### Okay, so what about maintainability?

Let's say we have another component other than an input that takes the same props:

```typescript
interface ComponentProps {
  setValue: Dispatch<SetStateAction<string>>;
  value: string;
}
```

But, what if we update the app and now want this to take `string | number`, or even `string | number | null`?

```typescript
interface ComponentProps {
  setValue: Dispatch<SetStateAction<string | number | null>>;
  value: string | number | null;
}
```

Not only is that starting to look a bit messy and long-winded, but it also means us having to update our types in 2 places at once. I do not care if those 2 places are right on top of each other, we are developers and therefore do not like updating the same thing in 2 places at once!

###### Enter self-referencing types

Lukily there is a little trick that will allow us, within a type declaration, to reference the very same type we are building, meaning we can define `string | number | null | X | Y | Z` on one line and reference it elsewhere.

```typescript
interface ComponentProps {
  setValue: Dispatch<SetStateAction<ComponentProps['value']>>;
  value: string | number | null;
}
```

This now tells the `setValue` prop that it is a dispatcher for a SetStateAction for whatever type `value` happens to be.

Now, the eagle-eyed among you will have noticed that `ComponentProps['value']` is actually more characters than `string | number | null`, so why are we making our description even longer? Sure, it explains _why_ the type is how it is, whereas with it set to `Dispatch<SetStateAction<string | number | null>>` we might have to do some digging to work that out, but is there an even neater way of doing this?

###### Enter generics

Generics allow us to save so much time when defining our types, because we needn't worry so much about typing things differently if an input happens to be a number instead of a string, or a boolean instead of a number. We simply pass `T` and let it do its thing.

So in order to make our interface generic we do:

```typescript
interface ComponentProps<T> {
  setValue: Dispatch<SetStateAction<T>>;
  value: T;
}
```

Then to make use of this new functionality we can go back to our ControlledInput and declare it as:

```typescript
function ControlledInput({ setValue, value }: ComponentProps<string>) {}
```

Which means "hey, I want to have these props, and assign these types to them, based on the fact I'm working with a string".

This then opens the door to a reusable set of props that you can import into any component inside your app, and even extend from there.

```typescript
export interface ComponentWithStateValueAndSetterProps<T> {
  setValue: Dispatch<SetStateAction<T>>;
  value: T;
}
```

```typescript
type ControlledTextInputProps = ComponentWithStateValueAndSetterProps<string>;

export function ControlledTextInput({ setValue, value }: ControlledTextInputProps) {
  return <input type="text" value={value} onChange={e => setValue(e.currentTarget.value)} />
}
```

###### Final step

This is all well and good, but now how do I add things like an `id` or `className` to my input, TypeScript won't let me!

Well, we can build upon more than 1 base type, depending on whether you prefer `interface` or `type` there are a couple of different ways of doing this.

```typescript
interface ControlledTextInputProps extends HTMLProps<HTMLInputElement>, ComponentWithStateValueAndSetterProps<string>;
// OR
type ControlledTextInputProps = HTMLProps<HTMLInputElement> & ComponentWithStateValueAndSetterProps<string>;

export function ControlledTextInput({ setValue, value, ...inputElementProps }: ControlledTextInputProps) {
  return <input type="text" value={value} onChange={e => setValue(e.currentTarget.value)} {...inputElementProps} />
}
```

`HTMLProps` is another import from React that provides all the props that we can add directly to whichever element we pass as a generic, in this case, `HTMLInputElement`. It makes them all optional so that we can pass in as many or as few as we want and name them exactly the same as if we were adding them to the element itself.

So now the component will take a whole host of additional props, such as `className` or `id` and apply them to the `input` element.

###### One extra consideration

TypeScript has a whole bunch of native types available, many of which you will probably never actively use. With this in mind, you can restrict `T` to a limited set of types, if you wanted to restrict it to be either a number or a string you'd do `interface ComponentWithStateValueAndSetterProps<T extends string | number>`. Be mindful with this approach though, as you may find yourself accidentally disallowing certain types that you will make use of in state, such as arrays or objects.

###### Before:

```typescript
interface ComponentProps {
  setValue: () => void;
  value: string;
}
```

 - Shorter (hey, I had to find _something_ as a pro!)
 - Not type-safe
 - Less descriptive
 - Not customisable
 - Reusable if and only if multiple components take the same props
 - Easily updateable (because of the lack of type-safety)

###### After:

```typescript
interface ComponentWithStateValueAndSetterProps<T> {
  setValue: Dispatch<SetStateAction<T>>;
  value: T;
}
```

 - Longer (likewise, I had to find something as a con)
 - Type-safe
 - More descriptive
 - Customisable
 - Easily reusable
 - Easily updateable (with no loss of type-safety, in fact, would it even need updating at all, due to the generic?)

Always remember that you're not writing code just for today, you're writing code for the person who will be using your application next year, the QA team who will be testing it in 5 years, and the developer who will be maintaining it in 10 years. Keep all of these people happy, because you might just be one of them!

Happy coding!