Manipulating non-TypeScript data strings - LogRocket Blog (2023)

In one of my recent projects, I had to deal with various custom representations of dates as strings, likeAAAA-MM-DDmiAAAAMMDD. Since these dates are string variables, TypeScript infers theropedefault type. While this is not technically incorrect, working with this type definition is complicated, making it difficult to work effectively with these date strings. For example,constant dog = 'alfie'is also inferred asropetype.

In this article, I'll show my approach to improve the developer experience and reduce potential errors when writing these date strings. Canfollow along with this essence. Let's start!

Index

  • Types of template literals
  • Predicate Constraint Type
  • Write date strings
  • Conclusion

Before we get into the code, let's briefly review the TypeScript functions we'll use to accomplish our goal.types of literal modelsminarrowing via type predicates.

Types of template literals

Introduced in TypeScript v4.1, template literal types share syntax with JavaScript template literals, but are used as types. Type template literals resolve to a union of all string combinations for a given template. This may sound a bit abstract, so let's see it in action:

type Person = 'Jeff' | 'Maria'type Greeting = `hi ${Person}!` // Template literal typeconst validGreeting: Greeting = `hi Jeff!` // // Note that the type of `validGreeting` is the union `"hi Jeff! " | "hello Maria!` const invalidGreeting: Greeting = `bye-bye, Jeff!` // // Write '"bye-bye, Jeff!"' cannot be assigned to type '"hello, Jeff!" | "Hello, Maria!"

Template literal types are very powerful and allow you to perform generic type operations on these types. For example,capitalization:

(Video) LogRocket TypeScript Meetup: TypeScript Alternatives – JSDoc JavaScript

type Person = 'Jeff' | 'Maria'type Greeting = `hi ${Person}!`type LoudGreeting = Uppercase<Greeting> // Template literal uppercase typeconst validGreeting: LoudGreeting = `HI JEFF!` // const invalidGreeting: LoudGreeting = `hi jeff!` // // Type '"Hello, Jeff!"' cannot be assigned to type '"HELLO, JEFF!" | "HELLO MARIA!"

Predicate Constraint Type

TypeScript does a phenomenal job of constraining types, for example in the following example:

leave age: string | number = getAge(); // `age` is of type `string` | `number`if (typeof age === 'number') { // `age` reduces to type `number`} else { // `age` reduces to type `string`}

That said, when dealing with custom types, it can be useful to tell the TypeScript compiler how to do the narrowing, for example, when we want to constrain to a type after performing run-time validation. In this case, narrowing of type predicates or user-defined type protections are useful.

In the following example,isDog type guard helps to constrain types for animal variablechecking the type property:

type Dog = { type: 'dog' };type Horse = { type: 'horse' };// custom type guard, `pet is Dog` is the function of type isDog(pet: Dog | Horse): pet is Dog { return pet.type === 'dog';}let animal: Dog | Horse = getAnimal(); // `animal` is of type `Dog` | `Horse` if (isDog(animal)) { // `animal` reduces to type `Dog`} else { // `animal` reduces to type `Horse`}

Write date strings

Now that we're familiar with the basics of TypeScript, let's review our date strings. For the sake of brevity, this example will only contain the code forAAAAMMDDdate strings. All code is available atnext essence.

(Video) LogRocket TypeScript Meetup: Write more readable code with TS 4.4

First, we'll need to define the template literal types to represent the union of all date-like strings:

write one to nine = 1|2|3|4|5|6|7|8|9 write zero to nine = 0|1|2|3|4|5|6|7|8|9/** * Years */type YYYY = `19${zeronine}${zeronine}` | `20${zeroToNine}${zeroToNine}`/** * Months */MM type = `0${oneToNine}` | `1${0|1|2}`/** * Days */ DD type = `${0}${oneToNine}` | `${1|2}${zeronine}` | `3${0|1}`/** * YYYYMMDD */type RawDateString = `${YYYY}${MM}${DD}`;const date: RawDateString = '19990223' // const dateInvalid: RawDateString = ' 19990231' //February 31 is not a valid date, but the literal model doesn't know that! const dateWrong: RawDateString = '19990299' // Write error, 99 is not a valid day

As seen in the example above, template literal types help specify the shape of date strings, but there is no actual validation for these dates. Therefore, the compiler signals19990231as a valid date, even if it is not, since it matches the model type.

Also, by inspecting the above variables likedata,invalid data, miwrong date, you will find that the editor displays the union of all valid strings for these template literals. Although useful, I prefer to definenominal classificationso that the type of valid date strings isdate stringinstead of"19000101" | "19000102" | "19000103" | .... The nominal type will also be useful when adding user-defined type protections:

type Brand<K, T> = K & { __brand: T };type DateString = Brand<RawDateString, 'DateString'>;const aDate: DateString = '19990101'; // // El tipo 'string' no se puede asignar al tipo 'DateString'

To ensure that ourdate stringtype also represents valid dates, we'll set up a user-defined type guard to validate dates and constrain types:

(Video) LogRocket Meetup: Understanding API data-fetching methods in React

/** * Use `moment`, `luxon` or another date library */const isValidDate = (str: string): boolean => { // ...}; // user-defined type guardfunction isValidDateString(str: string ): str is DateString { return str.match(/^\d{4}\d{2}\d{2}$/) !== null && isValidDate (str);}

Now, let's look at the date string types in some examples. In the code snippets below, user-defined type guarding is applied to constrain the type, allowing the TypeScript compiler to refine types to more specific types than declared. Type protection is then applied in a factory function to create a valid date string from a raw input string:

/** * Usage in type constraint */// valid string format, valid data const date: string = '19990223';if (isValidDateString(date)) { // evaluates to true, `date` is reduces to type `DateString` }// valid string format, invalid date (February does not have 31 days) const dateWrong: string = '19990231';if (isValidDateString(dateWrong)) { // evaluates to false, `dateWrong` is not a valid date, even if its format is YYYYMMDD }/** * Use in function factory */function toDateString(str: RawDateString): DateString { if (isValidDateString(str)) return str; throw new Error(`Invalid date string: ${str}`);} // Valid string format, valid dateconst date1 = toDateString('19990211'); // `date1`, is of type `DateString` // invalid string formatconst date2 = toDateString('asdf'); // Type error: argument of type '"asdf"' cannot be assigned to parameter of type '"19000101" | ...// valid string format, invalid date (February does not have 31 days) const date3 = toDateString('19990231'); // throws error: invalid date string: 19990231

Conclusion

I hope this article sheds some light on what TypeScript is capable of in the context of writing custom strings. Note that this approach also applies to other custom strings, such as customUser ID,user-XXXXand other date strings likeAAAA-MM-DD.

The possibilities are endless when you combine user-defined type protections, template literal strings, and nominal types. Be sure to leave a comment if you have any questions and happy coding!

log rocket: full visibility of your web and mobile applications

log rocketis a front-end application monitoring solution that allows you to reproduce issues as if they were happening in your own browser. Instead of guessing why errors occur or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works seamlessly with any application, regardless of framework, and has plugins to register additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket logs console logs, JavaScript errors, stack traces, network request/responses with headers and bodies, browser metadata, and custom logs. It also leverages the DOM to write HTML and CSS to the page, recreating pixel-perfect video from even the most complex single-page and mobile apps.

(Video) LogRocket GraphQL Meetup: Rapid development with GraphQL Codegen

try it free.

(Video) Capture Frontend Logs & User Insights with Log Rocket & React

Videos

1. LogRocket Meetup: How to start a blog using Docusaurus, GitHub Actions, and Azure Static Web Apps
(LogRocket)
2. How to test Vite projects using Vitest
(LogRocket)
3. No BS TS 34 - Typescript Decorators
(Jack Herrington)
4. How to use CSS variables like a pro | Tutorial
(LogRocket)
5. 5 ways to make Node.js HTTP requests
(LogRocket)
6. LogRocket URQL meetup: GraphQL, the URQL library, & building advanced features on the client-side
(LogRocket)
Top Articles
Latest Posts
Article information

Author: Msgr. Refugio Daniel

Last Updated: 05/03/2023

Views: 5841

Rating: 4.3 / 5 (74 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Msgr. Refugio Daniel

Birthday: 1999-09-15

Address: 8416 Beatty Center, Derekfort, VA 72092-0500

Phone: +6838967160603

Job: Mining Executive

Hobby: Woodworking, Knitting, Fishing, Coffee roasting, Kayaking, Horseback riding, Kite flying

Introduction: My name is Msgr. Refugio Daniel, I am a fine, precious, encouraging, calm, glamorous, vivacious, friendly person who loves writing and wants to share my knowledge and understanding with you.