Troubleshooting issue with TypeScript: Union types not functioning correctly when mapping object values

When it comes to mapping object values with all primitive types, the process is quite straightforward:

type ObjectOf<T> = { [k: string]: T };

type MapObj<Obj extends ObjectOf<any>> = {
  [K in keyof Obj]: Obj[K] extends string ? Obj[K] : 'not string';
};

type Foo = MapObj<{
  a: 'string',
  b: 123,
}>; // Foo is { a: 'string', b: 'not string' }

However, things get tricky when unions are involved as object values in TypeScript:

type AllPaths = '/user' | '/post';

type Props<Path extends AllPaths> = MapObj<{
  path: Path,
}>;

function Fn<Path extends AllPaths>({ path }: Props<Path>) {
  const path2: AllPaths = path;
}

Encountering the error:

Type 'Path extends string ? Path : "not string"' is not assignable to type 'AllPaths'.
  Type 'Path | "not string"' is not assignable to type 'AllPaths'.
    Type '"not string"' is not assignable to type 'AllPaths'.
      Type 'Path extends string ? Path : "not string"' is not assignable to type '"/post"'.
        Type 'Path | "not string"' is not assignable to type '"/post"'.
          Type 'string & Path' is not assignable to type '"/post"'.

Even though each member of the union is a string, the output of MapObj does not remain as unions of strings. How can this issue be resolved?

TS Playground: https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gRgKwgY2DAZgHgCoD4oC8UA3lANoDWAXFAM7ABOAlgHYDmAujdlAL4DcAKEGhIUALIBDMPASZZUCAA9gEFgBNasRCjRZJLELnxFigqOQDSUVlAoQQAe3TaEXV2UsdFKtZrqMrGxQAPweXlA0AOQsjsABzOxRQgLCotAAggA2WQAKksAAFlpEUQD0AK60EAxRUAA+UOVgjvTJaeDQuQyOYLSY+UU+qhpa2XkFxSYS0rKYZhZgkzSDhQA0gry4QoLoFSyoTI4sUABiLAOTw35jOau0uAAUpEtDvCs9fZdFuACUJOYoMhjvQoK9CgAmGjje6EMGTFKCIA

Answer №1

My observation is that when you define

type Props<Path extends AllPaths> = MapObj<{ path: Path, }>;
in this way, it will only include the values '/user' | '/post'. However, TypeScript's static type checking may not fully recognize this, as the type is MapObj<{ path: Path, }> which represents a MapObj of a JavaScript Object.

One solution is to simply use as Path to make a compromise.

function Fn<Path extends AllPaths>({ path }: Props<Path>) {
  const path2: AllPaths = path as Path;
}

Alternatively, you can define a type for PathObj like this:

type AllPaths = '/user' | '/post';
type AllPathObj = {
  path: AllPaths,
}

type Props<PathObj extends AllPathObj> = MapObj<AllPathObj>;

function Fn<PathObj extends AllPathObj>({ path }: Props<PathObj>) {
  const path2: AllPaths = path;
}

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

In the event that the API server is offline, what is the most effective way to notify users that the server is not accessible on the client-side?

I am working on a project where my website interacts with an API hosted on a different server. The website makes API calls and displays the data in a table. However, I want to implement a way to alert the user client-side using JavaScript if the API server ...

What is the best way to change the folder name when hovering over it?

Typically, my website displays categories with images from the /thumb-min/ directory. For example, 95-IMG_6509.JPG is loaded like this: /thumb-min/95-IMG_6509.JPG When I navigate to the details page, it loads the image from: /thumb-medium/95-IMG_6509.JP ...

Transferring an ES6 class from Node.js to a browser environment

I've been exploring ways to bundle a super basic ES6-style class object for the browser. Here's an example: class Bar { constructor(){ this.title = "Bar"; } } module.exports = Bar; While I can easily utilize this in my node projec ...

Has anyone else encountered the issue where the JavaScript for Bootstrap version 4.3.1 is malfunctioning on Firefox version 65.0.1?

While browsing through the bootstrap v 4.3.1 documentation on firefox v 65.0.1, I noticed an issue with the javascript not functioning properly. For instance, the carousel component is not progressing to the next slide with its transition animation as it s ...

Instructions on how to save HTML "innerHTML" along with attributes to a text document

I'm currently developing an HTML export feature from a DIV tag that includes various elements and attributes. HTML: <div id="master"><span class="classname">content goes here</span></div> <span class="download" onclick="ca ...

Dynamically insert the ng-if attribute into a directive

In my code, I have implemented a directive that adds an attribute to HTML elements: module1.directive('rhVisibleFor', function ($rootScope) { return{ priority: 10000, restrict: 'A', compi ...

How can you ensure an interface in typescript 3.0 "implements" all keys of an enum?

Imagine I have an enum called E { A = "a", B = "b"}. I want to enforce that certain interfaces or types (for clarity, let's focus on interfaces) include all the keys of E. However, I also need to specify a separate type for each field. Therefore, usi ...

Errors may arise in Typescript when attempting to block the default behavior of DataGrid onRowEditStop

Hey there! I'm new to posting questions here and could use some help. I'm encountering a minor issue while trying to prevent the default behavior of the "Enter" key in the "onRowEditStop" method of the DataGrid component. Here's my code sni ...

I am looking to have the datepicker automatically clear when the reset button is clicked

this code snippet is from my component.ts file resetFilters() { this.date = 0; this.query.startedAt= null; this.query.endedAt=null; this.searchTerm = ''; this.route.params.subscribe((params) => { this.machineId = Numb ...

How can I resolve a promise that is still pending within the "then" block?

Here is a piece of code that I have written: fetch(`${URL}${PATH}`) .then(res => { const d = res.json(); console.log("The data is: ", d); return d; }) When the code runs, it outputs The data is: Promise { <pending> ...

Checking dates in a JavaScript form

var myForm = document.getElementById("form"); document.bgColor="#FFFFCC"; //page styling myForm.style.color="blue"; myForm.style.fontSize="20px"; myForm.style.fontWeight="400"; myForm.style.fontFamily="arial"; function validateForm() { var firstname = d ...

Dealing with checked input type='checkbox' in React - A guide

Having a set of checkboxes, some already checked and some to be updated by the user. The issue here is that while the checkboxes render correctly initially, they do not change upon clicking. The 'checked' value does get updated when onChange is t ...

When I submit 'name' through Postman, ValidatorExpress notifies me that the input for 'name' is missing

Whenever I use the 'POST' method in Postman with the URL http://localhost:7777/register, and select the options Body and row to paste the object {name: 'Martin}, why does it return "You must supply a name!" from the array ["You must supply a ...

Is it possible to have a synchronous function imported in typescript?

// addons.ts export interface addon { name: string; desc: string; run: (someparam: any) => void; } export function loadaddons(): Array<addon> { let addons: Array<addon> = []; fs.readdirSync(path.join(__dirname, "addons")) .fi ...

When using Node.js with Express and ssh2, my data structures remain intact without any resets when loading web pages

To display jobs sent by users to a cluster, the code below is used (simplified): var split = require('split'); var Client = require('ssh2').Client; var conn = new Client(); var globalRes; var table = [["Head_1","Head_2"]]; module.exp ...

AngularJS allows a function to return an array value, which can be displayed in separate blocks on

Building a program that involves working with an AngularJS array. I need to showcase the elements of the AngularJS array, returned by a function, in an organized manner. Each element should be displayed correspondingly - for example, 'first' cont ...

Font size for the PayPal login button

I am looking to adjust the font size of the PayPal Login button in order to make it smaller. However, it appears that the CSS generated by a script at the bottom of the head is overriding my changes. The button itself is created by another script which als ...

The hover state of a div will not be lost if its parent element is not being hovered over

When hovering over the second, third, or fourth item, hidden text will appear on the left side. If you hover your cursor over the hidden text, it will disappear again. I want to be able to hover over the second item, move my cursor to "hide", and click o ...

Should the null-forgiving operator be avoided when using `useRef`?

Is the following code snippet considered poor practice? const Component: React.FC<{}> = () => { const ref = React.useRef<HTMLDivElement>(null!); return <div ref={ref} />; } I'm specifically questioning the utilization of ...

Adjusting the placement in Draw2D

I'm a newcomer to this library and I'm struggling to figure out how to properly size and position the canvas. If anyone could provide guidance on the best way to do this, I would greatly appreciate it. $(window).load(function () { // se ...