TypeScript: Infer Types to Avoid Explicit Types
Tags: typescript, beginners, react
Published: 2023-11-21 04:59:47


The idea for this post came about while I was reviewing this pull request (PR) for [OpenSauced](https://opensauced.pizza).

{% embed https://github.com/open-sauced/app/pull/2168 %}

My friend Brittney Postma (@brittneypostma) who is a huge Svelte fan, wanted to add Svelte to the list of available interests from our explore page.

[![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ua2d5toq5zw880dxdwlg.png)](https://app.opensauced.pizza/javascript/dashboard/filter/recent)

She made some changes which worked while running the dev server, but TypeScript was complaining, causing the build to fail.

```bash
4:49:27 PM: ./lib/utils/recommendations.ts:3:7
4:49:27 PM: Type error: Property "svelte" is missing in type "{ react: string[]; javascript:
stringIl; python: string|]; ml: string|]; ai: stringI]; rust: string[l; ruby: string[]; c:
stringIl; cpp: string|]; csharp: string|]; php: string|]; java: string[]; typescript: string|];
golang: string||; vue: string||; kubernetes: string|]; hacktoberfest: string|]; clojure:
stringIl; }" but required in type "Record<"ruby" | "javascript" | "python" | "java" ||
"typescript" | "csharp" | "cpp" | "php" | "c" | "ai" | "ml" | "react" | "golang" | "rust" |
"svelte" | "vue" | "kubernetes" | "hacktoberfest" | "clojure", string[]>".
4:49:27 PM: 1 | import { interestsType } from "./getInterestOptions";
4:49:27 PM: 2
4:49:27 PM: > 3 | const recommendations: Record‹interestsType, string[]> = {
4:49:27 PM: ^
4:49:27 PM: 4 | react: ["Skyscanner/backpack"],
4:49:27 PM: 5 | javascript: ["EddieHubCommunity/LinkFree"],
4:49:27 PM: python: ["randovania/randovania"],
4:49:28 PM: Failed during stage "building site": Build script returned non-zero exit code: 2
```

I mentioned adding 'svelte' to the `topic` prop's union type in the [LanguagePillProps](https://github.com/open-sauced/app/blob/00486f7b45c7e185208030422f675718724c1d4a/components/atoms/LanguagePill/LanguagePill.tsx#L24-L47) interface in our `LanguagePill` component should resolve the issue. Narrator, it did.

Having to add `'svelte'` to the `topic` props type resolved the issue, but it was extra work. Typically, you want to **infer types as much as possible**.

**Just a note.** This is not criticizing Brittney’s pull request (PR). This post is about a potential refactoring I noticed while reviewing her PR which could improve the types' maintenance in the project.

## Examples of Type Inference

You might already be inferring types without realizing it. Here are some examples of types being inferred.

```typescript
let counter = 0
```

`counter` gets inferred as type `number`. You could write this as `let counter: number = 0`, but the explicit type is unnecessary.

Let's look at an example of an array

```typescript
let lotteryNumbers = [1, 34, 65, 43, 89, 56]
```

`lotteryNumbers` gets inferred as `Array<number>`. Again, you could explicitly type it.

```typescript
// Array<number> or the shorter syntax, number[]
let lotteryNumbers: Array<number> = [1, 34, 65, 43, 89, 56]
```

But once again, it's unnecessary. Take it for a spin in the [TypeScript playground](https://www.typescriptlang.org/play?#code/PTAEEsDsDMFMCd6wCagC4E8AOsIGdQBDSIxQjUAe2lEgFcBbAIwTwCgAbWNUDytNAgwA5Ri3gEAvKADaARgA0oAMwAWJQDYArEtXKlADgCcSrRoC6bNiFCwAHlg7gAxuDQcKmHEQLFS8cioaemZWTm4qNAALBAAZfkF4ETFWAC5QAEEyDAAeEPEAPlBpGXUVACZNHQrDU3MgA) to see for yourself.

Let’s look at a React example, since plenty of folks are using React. It’s pretty common to use `useState` in React. If we have a counter that resides in `useState`, it’ll get set up something like this.

```typescript
const [counter, setCounter] = useState<number>(0);
```

Once again, though, we don’t need to add an explicit type. Let TypeScript infer the type. `useState` is a [generic function](https://www.typescriptlang.org/docs/handbook/2/generics.html), so the type looks like this `useState<T>(initialValue: T)`

Since our initial value was 0, T is of type `number`, so `useState` in the context of TypeScript can infer that `useState` is `useState<number>`.

## The Changes

I discussed the types refactor on my [live stream](https://nickyt.live) for anyone interested in a highlight from that stream.

{% embed https://www.twitch.tv/videos/1984891921 %}

And here's the PR I put up.

{% embed https://github.com/open-sauced/app/pull/2192 %}

I did some other refactoring in the pull request, but the big chunk of it was this diff.

```diff
interface LanguagePillProps {
-  topic:
-    | "react"
-    | "javascript"
-    | "python"
-    | "ML"
-    | "AI"
-    | "rust"
-    | "ruby"
-    | "c"
-    | "cpp"
-    | "csharp"
-    | "php"
-    | "java"
-    | "typescript"
-    | "golang"
-    | "vue"
-    | "Kubernetes"
-    | "hacktoberfest"
-    | "clojure"
+  topic: InterestType
  classNames?: string;
  onClick?: () => void;
}
```

`InterestType` is a type inferred from the `interests` array (see [getInterestOptions.ts](https://github.com/open-sauced/app/blob/beta/lib/utils/getInterestOptions.ts#L1-L22)).

```typescript
const interests = [
  "javascript",
  "python",
  "java",
  "typescript",
  "csharp",
  "cpp",
  "php",
  "c",
  "ruby",
  "ai",
  "ml",
  "react",
  "golang",
  "rust",
  "svelte",
  "vue",
  "kubernetes",
  "hacktoberfest",
  "clojure",
] as const;
export type InterestType = (typeof interests)[number];
```

Aside from the type being inferred, the type is now data-driven. If we want to add a new language to the `interests` array, all places where the `InterestType` are used now have that new language available. If there is some code that requires all the values in that union type to be used, TypeScript will complain.

![TypeScript complaining that the property 'svelte' is missing in type '{ react: any; rust: any; javascript: any; ai: any; ml: any; python: any; typescript: any; csharp: any; cpp: any; php: any; c: any; ruby: any; java: any; golang: any; vue: any; kubernetes: any; hacktoberfest: any; clojure: any; }' but required in type 'Record<"javascript" | "python" | "java" | "typescript" | "csharp" | "cpp" | "php" | "c" | "ruby" | "ai" | "ml" | "react" | "golang" | "rust" | "svelte" | "vue" | "kubernetes" | "hacktoberfest" | "clojure", StaticImageData>'.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wub4v6h58nxn41l60te0.png)

In fact, a new issue was opened today because an SVG for Svelte was missing in another part of the application.

{% embed https://github.com/open-sauced/app/issues/2195 %}

If the `InterestType` has been used everywhere, that error would have been caught by TypeScript, just like in the screenshot above.

## Counter Example: Explicit Types Required

Let’s look at another React example.

```typescript
const [name, setName] = useState();
```

We’re on the infer types hype and set up a new piece of state in our React application. We’re going to have a name that can get updated. Somewhere in the application, we call `setName(someNameVariable)` and all of a sudden, TypeScript is like nope! What happened? The type that gets inferred for

```typescript
const [name, setName] = useState();
```

is `undefined`, so we can’t set a name to a `string` type. This is where an explicit type is practical.

```typescript
const [name, setName] = useState<string | undefined>();
```

If the `string | undefined`, I recommend reading about [union types in TypeScript](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types).

## Typing Function Return Types

For return types in functions, there are definitely two camps. Some think that return types should always be explicitly typed even if they can be inferred, and others not so much. I tend to lean towards inference for function return types, but agree with Matt Pocock's take that if you have branching in your function, e.g. `if`/`else`, `switch`, an explicit return type is preferred. More on that in Matt's video.

{% embed https://www.youtube.com/watch?v=nwSe95uFN8E %}

As mentioned, inferred types are the way to go for most cases, but Kyle Shevlin (@kyleshevlin) messaged me after this blog post went out with another use case to explicitly type the return type.

If a function returns a tuple, you need to explicitly type the return type. Otherwise, the inferred return type will be an array whose items have the union type of all the array items returned.

![A TypeScript function returning a tuple even though the inferred type is not a tuple](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhql5ip1rr8657kj46cy.png)

You can see this in action in a [TypeScript playground](https://www.typescriptlang.org/play?#code/PTAEDkHtQUwDwA4BsCWBjFAXUmCeCZQBbGAQwDsBnUAd0LQoHJtMYkkcALFan0UnAFdkhGlk6gAJjABmKcjEk58MSgCgQoAJLYATjEyDdVfuX67dpXLXFc+KIsnRZlBADSgAFOUFEARjC6oAA+Xp4AlKAAvAB8oABukCiS4eEA2gC6ajKC5GiYKJBm+obGAIIAKsJIMABiufmF5FCYAOqQugDW8gDmEaAA3mqgoCVGZmkADB79sYMAvhmgavNqGmBlZvBOGHoG466EPYKkluSsqvxCIqABvaP7xorrNpgSb-Qd+vmHlB5pPn8gRmkTmiWSWRyeQKRQepXIlWqdQaMPIEQAXKAAb4AroQdE4uDJEshiMxsYsdMvKC4gNFst5kA) I made. 

## Wrap it up!

Types are great, and so is TypeScript, but that doesn't mean you need to type everything. Whenever possible, lean on type inference, and explicitly type when necessary.