What strategies can be used to prevent redundancy when defining strings in both type declarations and type guards?

I have encountered duplication in my code where allowed strings are declared twice, once in the type declaration and again in the type guard. How can I refactor my code to eliminate this redundancy?

// inspired by: https://github.com/mattdesl/parse-unit

export type ParsedValue = {
  value: number;
  unit: Unit;
};

type Unit =
  | "ch"
  | "ex"
  | "em"
  | "rem"
  | "in"
  | "cm"
  | "mm"
  | "pt"
  | "pc"
  | "px";

export function parseUnit(str: string | null): ParsedValue | null {
  if (!str) {
    return null;
  }

  var value = parseFloat(str);

  const match = str.match(/[\d.\-\+]*\s*(.*)/);
  const unit = match ? match[1] : "";
  if (!isUnit(unit)) {
    return null;
  }

  return { value, unit };
}

export function isUnit(str: string): str is Unit {
  return ["ch", "ex", "em", "rem", "in", "cm", "mm", "pt", "pc", "px"].includes(
    str
  );
}

EDIT:

After receiving some suggestions, here is the updated version of the code. Unfortunately, this version is not functioning properly. I am encountering an error stating

Type 'string' is not assignable to type '"ch" | "ex" | "em" | "rem" | "in" | "cm" | "mm" | "pt" | "pc" | "px"'.

// inspired by: https://github.com/mattdesl/parse-unit

export type ParsedValue = {
  value: number;
  unit: Unit;
};

const units = [
  "ch",
  "ex",
  "em",
  "rem",
  "in",
  "cm",
  "mm",
  "pt",
  "pc",
  "px",
] as const;

type Unit = typeof units[number];

export function parseUnit(str: string): ParsedValue | null {
  if (!str) {
    return null;
  }

  var value = parseFloat(str);

  const match = str.match(/[\d.\-\+]*\s*(.*)/);
  const unit = match ? match[1] : "";
  if (!isUnit(str)) {
    return null;
  }

  return { value, unit };
}

export function isUnit(str: string): str is Unit {
  return ((units as unknown) as Array<string>).includes(str);
}

Answer №1

To ensure the type of the array is not widened to string[], declare it as a standalone variable using as const. After that, define the Unit type as typeof units[number]:

const units = ["ch", "ex", "em", "rem", "in", "cm", "mm", "pt", "pc", "px"] as const;
type Unit = typeof units[number];
export function isUnit(str: string): str is Unit {
  return (units as unknown as Array<string>).includes(str);
}

The units is a ReadOnlyArray, so it cannot accept arbitrary strings for .includes. Therefore, it must first be converted to Array<string> by passing through unknown.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

choose a distinct value for every record in the table

My goal is to only change the admin status for the selected row, as shown in the images and code snippets below. When selecting 'in-progress' for the first row, I want it to update only that row's status without affecting the others. <td ...

Unable to locate the identifier 'SomeContext' - Receiving the same error message (ts 2304) when calling useContext(SomeContext) at every instance

I have been struggling to update a small app by transitioning from passing state through multiple components to utilizing context, but I can't seem to get it working again. If anyone could offer some guidance or point me in the right direction, I&apo ...

Typescript - Postpone defining generic type until invoking function

Trying to modify an existing API by defining a generic type to determine the key/value pairs that can be passed to the function, and also for IntelliSense purposes. (Working with vue.js in this case, but it's not crucial.) Here is the structure of th ...

The received data object appears to be currently undefined

I am currently using MapBox to display multiple coordinates on a map, but I am encountering an issue where the longitude and latitude values in my return object are showing up as undefined. Could there be a missing configuration that is preventing the data ...

Unable to bring in Vue component from NPM module

Hello there, I recently developed my own npm package for a navigation bar and I need to incorporate it into my main codebase. Currently, I am utilizing vue @components but I am struggling with integrating the imported component. If anyone has insight on h ...

The attribute "at" is not a valid property for an Array

As per the documentation on MDN Web Docs Array.prototype#at method, it should be a valid function. However, when attempting to compile in TypeScript, an error occurs stating that the method does not exist. public at(index: number): V { index = Math.floor ...

Prettier is stripping away TypeScript generic annotations from React class components

I've been attempting to create an ErrorBoundary using a class component in the following manner: class ErrorBoundary extends Component<ErrorBoundaryProps,ErrorBoundaryState> However, every time I run prettier on it, the portion <ErrorBoundar ...

Is there a way to create a reusable type annotation for declaring functions in Typescript?

type Func = (param:string) => void // implementing a function expression const myFunctionExpression:Func = function(param) { console.log(param) } Within the TypeScript code snippet above, I have utilized a type alias to define the variable in a func ...

Is there a way to retrieve all values in the pathname from a URL after the initial slash

Extracting pathname pathname: "/kids/dlya-malyshey/platya-i-yubki" By using the substr function, I will remove the initial slash location.pathname.substr(1,); Now, the result is kids/dlya-malyshey/platya-i-yubki The challenge is to extract all ...

Passing headers using a universal method in HTTP CRUD process

My service function is structured like this: Please note: I am required to work with cookies book(data: Spa): Observable<any> { return this.http.post(`${environment.apiURL}:${environment.port}/${environment.domain}/abc/my.json`, data, { ...

The issue with Cypress AzureAD login is that it consistently redirects users away from the intended Cypress window

Trying to incorporate the automation of Azure AD login using Cypress, I have been facing some challenges. Even after configuring the local session storage according to the instructions in this GitHub repository https://github.com/juunas11/AzureAdUiTestAu ...

Utilizing the Double Mapping Feature in React with Typescript

It seems I might be overlooking something, can someone guide me on how to properly double map in this scenario? I'm encountering an error on the second map: Property 'map' does not exist on type '{ departure: { code: string; name: strin ...

Access specific properties of an object in TypeScript using dot notation with the

Can we use dot notation to extract nested object elements in TypeScript? interface Test { customer: { email: string; name: { firstName: string; }; }; }; type PickedTest = ExtractNested<Test, "customer.name.firstName"> ...

Tips for limiting users to inputting only alphanumeric characters and excluding special characters in an input field using Angular 8

How can I prevent users from inputting special characters in an input field and only allow alphanumeric values? The code that I have implemented so far does not seem to be working as intended. When a user enters a special character, it still shows up in th ...

What is the process for invoking a Promise<Response>?

Using the fetch method, I make a request to an API to retrieve JSON data. The custom function that I created returns a Promise<Response> (this is a simplified version). const getData = (): Promise<Response> => fetch('http://the.api.com ...

Refreshing Form in Angular 5 post submission

<form class="form-horizontal" name="form" (ngSubmit)="!f.form.invalid && staffDetails(model)" #f="ngForm" novalidate> <div class="form-group"><button [disabled]="f.invalid" *ngIf ="buttonSave" class="btn btn-info">Save</butt ...

In order to showcase the data from the second JSON by using the unique identifier

SCENARIO: I currently have two JSON files named contacts and workers: contacts [ { "name": "Jhon Doe", "gender": "Male", "workers": [ "e39f9302-77b3-4c52-a858-adb67651ce86", "38688c41-8fda-41d7-b0f5-c37dce3f5374" ] }, { "name": "Peter ...

Encountering an issue during an upgrade from Angular version 8 to 15 with an error message stating "Unable to utilize import statement outside of a

I have been attempting to upgrade my Angular app from version 8 to version 15. After updating all the necessary packages, I encountered the following error when running the application: Uncaught SyntaxError: Cannot use import statement outside a module (at ...

The issue of two-way data binding not functioning properly when using ng-select in Angular versions 9 and above has

I've encountered an issue in my Angular project where I'm trying to set a default value for the ng-select dropdown, but it doesn't seem to be working properly. The dropdown does not update when there is a change. Let's take a look at t ...

Unable to get the Angular Formly select option to bind

I'm currently working on binding formly select type options with the following code: fieldGroup: [ { key: 'TimeOffTypeID', type: 'select', className: 'flex-40 padding-10', templateOptions ...