Development
22 May 2024
Talon.One Writer
Content Team
Disclaimer: This article will use TypeScript in the context of React and assumes basic knowledge about React.
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.
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!
Let’s say we have a JsInput component that is a simple wrapper around an input field. It could look like this:
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.
The most straightforward approach is to simply annotate types inline, enforcing a check on the props passed to our Input component.
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:
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:
Thank you squiggly red lines!
But we also can’t pass any prop that we have not defined and assigned a type to:
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!
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:
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.
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:
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.
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?
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:
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?
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:
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:
You'll always be there for me, squiggly red lines.
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!
Join thousands of marketers and developers getting the latest loyalty & promotion insights from Talon.One. Every month, you’ll receive:
Loyalty and promotion tips
Industry insights from leading brands
Case studies and best practices
Isabelle Watson
Loyalty & promotion expert at Talon.One
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
Product
Company