Is there a way to restrict the type of the value returned by URLSearchParams.get() to a specific union type?

When handling a search parameter in the URL, such as ?mode=view, it is important to validate the value of mode to ensure it is either 'edit' or 'view'. To achieve this, a custom type called ModeTuple is created and converted to a union type using indexed access types ModeTuple[number].

type ModeTuple = ['view', 'edit'];

const searchParams = new URLSearchParams();
const modes: ModeTuple = ['view', 'edit'];

const m1: ModeTuple[number] = modes.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view';

The goal is to verify if the value of mode is valid by using modes.includes(...), which should narrow the type down to 'edit' | 'view'.

An error occurs:

Argument of type 'string' is not assignable to parameter of type '"view" | "edit"'

This suggests that the Array.prototype.includes method may not successfully narrow the type from string to

"view" | "edit"
. Despite the logic in the JavaScript code dictating that the mode value must be either 'view' or 'edit', TypeScript does not recognize this.

TypeScript Playground

Answer №1

const check: ModeTuple[number] = modes.includes(searchParams.get('mode')) ? searchParams.get('mode') : 'view';
will not function properly for a couple of reasons:

  1. mode.includes requires a ModeTuple[number] parameter, but searchParams.get('mode') returns string | null.

  2. The type narrowing done by TypeScript is limited. Even if #1 was not an issue, it would not be able to infer from

    modes.includes(searchParams.get('mode'))
    that the subsequent call to searchParams.get('mode') will return a ModeTuple[number]. (This is because not all functions are pure, so TypeScript cannot assume that a function will return the same result when called twice with the same arguments.)

For situations like this, I prefer starting with a constant array and deriving types from it:

const modes = ["view", "edit"] as const;
type ModeTuple = typeof modes; // If you want it
type Mode = ModeTuple[number];

This way, values are only listed once, which reduces chances of errors when modifications need to be made.

Next, you could replace your original m1 code with:

const m1 =
    modeString !== null && (modes as readonly string[]).includes(modeString)
    ? modeString as ModeTuple[number]
    : "view";

...but inline type assertions like that can be verbose and error-prone with repeated use.

I personally prefer defining a single type predicate (also known as "type guard") function that can be reused:

function isMode(mode: string | null): mode is Mode {
    return mode !== null && (modes as readonly string[]).includes(mode);
}

Then, you could do:

const modeString = new URLSearchParams(location.search).get("mode");
const m1 = isMode(modeString) ? modeString : "view";

Or, create a function for this purpose if needed in multiple places:

function getMode(searchParams: URLSearchParams): Mode | null {
    const mode = searchParams.get("mode");
    // Or perform the check here directly if `isMode` is not defined
    return isMode(mode) ? mode : null;
}

Now, getting the value becomes simpler:

const m1 = getMode(theSearchParams):

Playground link

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

Enhance your FullCalendar experience with React by displaying extra information on your calendar

I am new to using React and FullCalendar, and I have a page layout similar to the image linked below. https://i.sstatic.net/MooTR.png Additionally, I have a list of events structured as shown: id: "9", eventId: "1", ...

When utilizing the useRef hook in Material-UI, an error may arise stating: "The property 'value' is not found on the type 'never'."

Currently, I am utilizing material UI to construct a login and registration page. In the process, I am leveraging the useRef hook to retrieve a reference instance for a TextFiled, and using xxxRef.current.value to access the input value. Despite being abl ...

Tips on joining property name in typescript

Attempting to pass values between components using inheritance but encountering difficulties. Is it possible to achieve this through a service? If so, how can I overcome this issue? Any assistance in finding the solution would be greatly appreciated. bus. ...

tips for closing mat select when clicked outside

When a user clicks on the cell, it should display the value. If the user clicks outside the cell, the input field will close and show the field value. I am facing an issue on how to implement this with mat select and mat date picker. Any suggestions? Than ...

Enhancements to managing universal configuration object across the entire application

My current project involves working on an application with multiple products. To streamline product-specific configuration and eliminate the need for excessive if-else statements, I am looking to implement product-specific config keys that are consistently ...

Do I have to create all the classes returned when consuming a JSON web service in Angular/Typescript?

I would like to access this service: https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY I am interested in extracting only two pieces of data: "location" : { " ...

Is there a way to transfer ngClass logic from the template to the TypeScript file in Angular?

I am implementing dropdown filters for user selection in my Angular application. The logic for adding classes with ngClass is present in the template: <div [ngClass]="i > 2 && 'array-design'"> How can I transfer this ...

The React Nested Loop Query: Maximizing Efficiency in Data

Learning React has been a challenge for me, especially when comparing it to XML/XPath. In this scenario, I have two arrays simplified with basic string properties... customerList: Customer[] export class Customer { id: string = ""; firstnam ...

How can we direct the user to another tab in Angular Mat Tab using a child component?

Within my Angular page, I have implemented 4 tabs using mat-tab. Each tab contains a child component that encapsulates smaller components to cater to the specific functionality of that tab. Now, I am faced with the challenge of navigating the user from a ...

Retrieve the thousand separator for numbers using Angular in various languages

When using the English locale, numbers appear as follows: 111,111,222.00, with a comma as the thousand separator and a point as the decimal separator. In languages like German, the same number would be represented as 111.111.222,00, reversing the positions ...

The specified 'IArguments' type does not qualify as an array type

Currently working on crafting a personalized logger. It's a fairly straightforward process, but I'm running into some errors that are muddying up my output. Here's what I have so far: @Injectable() export class Logger { log(...args: any ...

Expand the size of the imported gltf model within Three.js

After successfully loading a 3d model with a gltf extension using the GLTFLoader in Three.js, I encountered a new challenge. I needed to adjust the dimensions of the model dynamically when the window is resized, based on the values of window.innerWidth and ...

Modifying the menu with Angular 4 using the loggedInMethod

Struggling to find a solution to this issue, I've spent hours searching online without success. The challenge at hand involves updating the menu item in my navigation bar template to display either "login" or "logout" based on the user's current ...

How do you transfer byte[] data using a DTO in Java Spring?

I am currently facing an issue with my server-side application. The problem arises when attempting to convert a Blob to an Excel file on the front-end, specifically when the byte[] is sent within a DTO. When sending a POST request from the back-end (sprin ...

Error Found: Unexpected Colon (:) in Vue TypeScript File

Important Update: After thorough investigation, it appears that the issue is directly related to the boilerplate being used. As a temporary solution, it is recommended not to extract the TypeScript file but keep it within the .vue file for now. In a sim ...

When using Typescript inheritance, the datatypes shown in IntelliSense are unexpectedly listed as "any" instead of

In my Typescript code, I have a small implementation where a class is either implementing an interface or extending another class. interface ITest { run(id: number): void } abstract class Test implements ITest { abstract run(id); } class TestEx ...

404 Error: Unable to Locate Socket Io

I'm currently working on implementing a chat feature in Angular 2 using Socket IO, following this tutorial. However, I encountered an error message during a test on the server: GET http://localhost:3000/socket.io/?EIO=3&transport=polling& ...

Having trouble assigning a value of `undefined` to a TextField state in React hook

I am in need of setting the initial state for a TextField date to be undefined until the user makes a selection, and then allowing the user an easy way to reset the date back to undefined. In the code snippet below, the Reset button effectively resets par ...

Using ngModel instead of value to bind a custom Angular directive for currency input

Currently, I am using a specialized mat-input-currency format directive to automatically convert my field inputs into currency. You can find the npm repository here. However, the directive binds the element data to [value] of the input, and I require it t ...

Unable to find custom components when using react-router

My goal is to improve the organization of my Routes in React and separate concerns. I am currently utilizing react-router-dom version 5. Within my Application Routes component, I have structured it with 3 children components: AuthenticatedRoutes PublicRo ...