arrow_back

Development

22 May 2024

Types of TypeScript typings

Talon.One Writer

Content Team

Types of TypeScript typings Talon.One
access_time_filled

6 minutes to read

Disclaimer: This article will use TypeScript in the context of React and assumes basic knowledge about React.

Freedom of choice is hard

It is obvious these days that TypeScript is here to stay in the web dev world, especially combined with React. However, many developers have had no experience with strongly typed languages, and the TypeScript documentation can be intimidating.

image

Hmm yes, I see.

One of the reasons for this is that TypeScript (TS for short) offers us many ways to leverage its type system, while not explicitly recommending any specific approach. In this article, we will explore some of these approaches. It will only scratch the surface of what is possible, though!

TLDR; There are many ways to make your code safer with TypeScript. Jump directly to the end of the story or go directly to the CodeSandbox to play around with the code we will write!

In the beginning, there was JavaScript

Let’s say we have a JsInput component that is a simple wrapper around an input field. It could look like this:

image

Vanilla JavaScript input

This is fine. But nothing prevents us from passing some outlandish variable as value, and we will only know at runtime when we get unexpected results or even a nice crash. That is exactly the problem that TS solves, by offering us type errors at compile time, before you try to run your code. And, even better, directly in our IDE. Let’s see how.

Inline types

The most straightforward approach is to simply annotate types inline, enforcing a check on the props passed to our Input component.

image

TypeScript input with inline types

Here we say that props will be an object with the key's value and label. value can be either a string or a number, and label will always be a string. We can now know at any point what type we are dealing with:

image

You will get addicted to this

And we are already protected against a whole class of bugs by squiggly red lines in the IDE (and noisy scary errors in the terminal)! We can’t pass an object as value:

image

Thank you squiggly red lines!

But we also can’t pass any prop that we have not defined and assigned a type to:

image

I’m a fan of the squigglies

This feels very close to writing propTypes for every component, with a more pleasant syntax. And with only this very simple step, we have already made this code orders of magnitude safer. But there is more!

Defining a type

Maybe we don’t want out types to be so tightly coupled with our component. Maybe we use the same Input in many places! Then maybe it is time to define a type we will also be able to reuse anywhere. This is how simple it is:

image

TypeScript input with defined types

Now, any time we need to pass a value and a label to an Inputcomponent, we can use the types we created for that express purpose! For this very simple example, this would be fine in real life. But let’s go deeper.

Defining an interface

An interface defines an entity, representing a contract that must be followed. It can contain any number of properties, and TS uses duck typing to define if we are conforming to the contract or not. If it looks ok, it will be accepted by the compiler. To put it simply, it is like an object, where keys are associated with types instead of values:

image

TypeScript input typed with an interface

Doesn’t it looks nice? We have used our previously defined types to define an interface that specify the contract the props passed to InterfaceTsInput must:

  • be an object

  • have a value key which must be of type InputValue, that is to say string or number

  • have a label key which must be of type Label, that is to say a string

To be fair, this is also possible with a simple type, like so:

type InputProps = {
 value: InputValue;
 label: Label;
}

So you might be wondering : “What is the difference between type and interface then?”

Don't worry, you're not alone.

image

When 14 million people ask the same question

The answer is subtle. The main difference is that an interface can be extended (we’ll do it in the next part), while a type cannot. As the more flexible and powerful tool, interface is generally preferred for anything more complex than primitive types.

We're starting to feel safer, aren’t we? But there’s one last thing. Are you ready to get into that weird syntax we saw at the start of the story?

Using generics

Generics are one of the core features of TS. It gives us the possibility to abstract types. This means passing types like we would pass arguments to a function. Ideally, it leads to extremely reusable and composable types. In practice, it is very easy for it to get out of hands and introduce a whole new world of complexity to your app. To be used wisely. In our example, it could look like this:

image

Here it is! The dreaded T! You can think of it simply as a placeholder, an argument name for the type that we then pass to our GenericInputinterface.

That T is then narrowed down when we define GenericTSInput: it extendsInputValue, meaning that now T can only be of the types defined by InputValue (string or number). But where do we pass the T, then? How will our component ever know what T is?

image

Before now, nothing had changed in the way we use our Input component. We would use it just as we would a regular React component. This changes when we introduce generics, because we need to specify the type of T. And as you can see in the gist above, this is how we do it, and what it means:

image

By specifying in those brackets that T is a number or a string, we are narrowing down the possible types of InputValue to be only one or the other. And of course, TS doesn’t allow us to break this rule:

image

You'll always be there for me, squiggly red lines.

Freedom of choice is still hard

As we have seen, TypeScript’s type system is a spectrum. It provides you with a number of building blocks for you to use as you see fit. It can be as simple or as complex as you need it to be. Emphasis on need. As ever, and even more in TypeScript’s case, premature optimization will cause some grief ultimately.

Our TypeScript code should always serve the same core goals, and adopt the simplest solution that satisfies them:

  • make your code safer to write with instant feedback on type errors from your IDE or the compiler

  • easier to maintain by ensuring you are not breaking any contracts your types have defined when adding more features

  • more readable for people new to the codebase by giving them some context about your data structures

I encourage you to experiment with the code we have been through in this CodeSandbox so you can get a better feel for what works and what doesn’t, And maybe you can try to add the missing onChange handler to all these inputs to get rid of that error!

Monthly loyalty newsletter

Join thousands of marketers and developers getting the latest loyalty & promotion insights from Talon.One. Every month, you’ll receive:

check_circle

Loyalty and promotion tips

check_circle

Industry insights from leading brands

check_circle

Case studies and best practices

Newsletter author

Isabelle Watson

Loyalty & promotion expert at Talon.One

Talon.One Logo

The World's Most Powerful Promotion Engine

BERLIN

Wiener Strasse 10
10999 Berlin
Germany

BIRMINGHAM

41 Church Street
B3 2RT Birmingham
United Kingdom

BOSTON

One Boston Place, Suite 2600
02108 Boston, MA
United States

SINGAPORE

1 Scotts Road, #21-10 Shaw Centre
228208 Singapore
Singapore

G2 LogoMach Alliance LogoISO 27001 Logo
CCPA Logo
GDPR Logo
SOC2 Logo

© 2024 Talon.One GmbH. All rights reserved.