My Typescript mistakes

Written on Wednesday, 22 December 2021 at 22:21.Β Last Modified on Saturday, 3 December 2022 at 16:06.

Tags: Typescript.

Coming from a typed background (C#, Java and Kotlin), there were quite a few mistakes to be made when first starting React Native development in 2017. And oh boy we did make them πŸ˜†.

We actually started using Flow (a static type checker for Javascript) right away, as it seemed to be the most mature option at that time. About 2 years later, we painfully migrated from Flow to Typescript. I’m pretty sure quite a few of our mistakes originate from this adoption and migration.

Typing when you shouldn’t

Typescript is really good in understanding what types are returned from a function.

This example also specifies the return type, however it’s really not needed as return a + b will always be a number and Typescript already knows this. Adding the return type just adds more code, more dead weight.

# no 🀒
# the return type can be perfectly implied by Typescript
function addNumbers(a: number, b: number): number {
  return a + b;
}
# yes πŸ˜„
function addNumbers(a: number, b: number) {
  return a + b;
}

Easier to refactor

If anyone would have the argument: but with the return type you also safeguard that the function only returns numbers; I would argue that, if the return type changes, code that uses this function will give a type error! And when that happens, it’s probably pretty intentional and you will have to adopt that other code πŸ˜‰.

No need to import other types

When using more complex data types from other functions, you would als need to import their returned types or even make an interface that uses the other types. While this overhead, is simply, not needed.

Prefer using helpers instead of manually importing the type

If I haven’t convinced you yet that defining types and exporting them probably isn’t the way forward, as this is more work that doesn’t benefit your code base, and even makes it harder to refactor. Let’s show you how you can use these instead:

ReturnType<typeof getCurrentWeather>

# no πŸ˜”
interface WeatherResponse {
  temperature: number,
  mood: string,
}
export const getCurrentWeather(): WeatherResponse => ({
  temperature: 3,
  mood: 'cold winter day',
});
export const otherMethod(currentWeather: WeatherResponse) => {
  // does something with current weather
}
# yes πŸ˜ƒ
export const getCurrentWeather() => ({
  temperature: 29,
  mood: 'great summer day',
});
export const otherMethod = (currentWeather: ReturnType<typeof getCurrentWeather>) => {
  // also does something with current weather
}

Awaited<ReturnType<>>

You want to use Awaited, but the promise is wrapped in a function? Combine Awaited & ReturnType!

export const getWeatherData = async() => {
  const response = await fetch('api.eliaslecomte.be/weather');
  const jsonResponse = await response.json();
  return jsonResponse;
}
export const main = (weatherData: Awaited<ReturnType<typeof getWeatherData>>) => {
  // does something with weather data
}

ComponentProps<>

I hope for you that you write React πŸ₯°, if you do, this will come in handy:

type buttonProps = ComponentProps<typeof Button>;
type onClick = ComponentProps<typeof Button>['onClick']

SagaReturnType<>

Are you still using redux-saga? This took us a while to discover, but there is a helper SagaReturnType<typeof yourGeneratorFunction>

const result: SagaReturnType<typeof getSomething> = yield call(getSomething);

Typescript 4.5’s Awaited

I was really glad that they finally added an Awaited out of the Typescript box! This simply allows you to use the returned type by a promise.

const simplePromise = new Promise((resolve, reject) => {
  setTimeout(() = resolve(true), 1000);
}
function simpleExample(promiseResult: Awaited<typeof simplePromise>) {
  // example
}

Inline type an any

If a function returns any, your goal is to make sure the function returns a proper type, or you use a helper to find out the type. Never just inline type it:

// no πŸͺ²
const age: number = await guessAge();
const height: number = 180;
// yes 🧹
const age = await guessAge();
const height = 180;

It’s not needed, typescript can infer most return types by itself. If somehow it can’t, just make sure to use a helper to get the correct type. If you inline type it, and the function all of the sudden returns a different type, you will not get warned about this mistake. Refactoring this code will be harder.

Null or undefined?

To be honest, I have been confused for a long time about this. Should it be null or undefined? My rule of thumb (thanks to my friend Bitcrumb) is that you should only use null if you want to explicitly signal an empty value.

Site with πŸ’• made by Eliaslecomte.be