Unable to set a union key type for dynamic objects

Within my object, I am dynamically assigning a field and its corresponding value:

type PhoneFields = 'deliveryPhoneNumber' | 'pickupPhoneNumber'


(props: {
 phoneField?: PhoneFields
}) {
  const initialValues = {
    [props.phoneField ?? 'deliveryPhoneNumber']: props.address.phone ?? ''
  }

I want to restrict the type so that only one of the union types can be used as a value for a key in the dynamic object:

type PhoneValues = {
  [x in PhoneFields]: string
}

However, when I attempt this, TypeScript throws an error:

Type '{ [x: string]: string; }' is missing the following properties from type 'PhoneValues': deliveryPhoneNumber, pickupPhoneNumber

How can I resolve this issue?

Updated

Upon following suggestions in the comments and making the key in the mapped type optional, the TypeScript error disappeared.

type PhoneValues = {
  [x in PhoneFields]?: string
}

Yet, I am uncertain about the advantages of defining a type in such a manner because even if I input a string that is not listed in PhoneFields, TypeScript does not flag it as an error. For instance:

const initialValues = {
  [props.phoneField ??  'xxxx']: props.address.phone ?? ''
} 

No error was thrown despite the use of 'xxxx' which is not part of the defined options.

Answer №1

The problem arises from the fact that you have not initialized the value for ['pickupPhoneNumber'].

To address this issue, you can set it up in the following manner:

type PhoneFields = 'deliveryPhoneNumber' | 'pickupPhoneNumber'

type PhoneValues = {
  [x in PhoneFields]: string
}

const f = (props: {
  phoneField?: PhoneFields,
  address: {
    phone: string, 
  }
}) => {
  const deliveryPhoneNumber = props.phoneField === 'deliveryPhoneNumber' ? props.address.phone : '';
  const pickupPhoneNumber = props.phoneField === 'pickupPhoneNumber' ? props.address.phone : '';
  const initialValues: PhoneValues = {
    ['deliveryPhoneNumber']: deliveryPhoneNumber,
    ['pickupPhoneNumber']: pickupPhoneNumber
  }
}

Alternatively, you can modify your PhoneValues as follows:

type PhoneValues = {
  [x in PhoneFields]: string | undefined
}

Then, this will function correctly:

(props: {
 phoneField?: PhoneFields,
  address: {
    phone: string, 
  }
}) => {
  const initialValues = {
    [props.phoneField ?? 'deliveryPhoneNumber']: props.address.phone ?? ''
  }
}

UPDATE

If you prefer having either deliveryPhoneNumber or pickupPhoneNumber (but not both) in the type, you can implement it like this:

type PhoneFields = 'deliveryPhoneNumber' | 'pickupPhoneNumber';

type PhoneValues = 
  { deliveryPhoneNumber: string, pickupPhoneNumber?: never }
  | { deliveryPhoneNumber?: never, pickupPhoneNumber: string }

const f = (props: {
  phoneField?: PhoneFields,
  address: {
    phone?: string, 
  }
}): PhoneValues => {
  const result: PhoneValues = {
    [props.phoneField ?? 'deliveryPhoneNumber']: props.address.phone ?? ''
  } as unknown as PhoneValues;
  console.log(result);
  return result;
}

f({phoneField: 'deliveryPhoneNumber', address: {phone: '123'}});
f({phoneField: 'pickupPhoneNumber', address: {phone: '456'}});
f({address: {phone: '789'}});
f({address: {}});

In the code above, a double assertion is used due to Typescript's inability to infer { [key: string]: string } as equivalent to PhoneValues, even when key is of type PhoneFields.

If you wish to avoid the double assertion, you could structure it as shown below:

type PhoneFields = 'deliveryPhoneNumber' | 'pickupPhoneNumber';

type PhoneValues = 
  { deliveryPhoneNumber: string, pickupPhoneNumber?: never }
  | { deliveryPhoneNumber?: never, pickupPhoneNumber: string }

const f = (props: {
  phoneField?: PhoneFields,
  address: {
    phone?: string, 
  }
}): PhoneValues => {
  let result: PhoneValues;
  switch (props.phoneField)  {
    case 'pickupPhoneNumber':
      result = {
        pickupPhoneNumber: props.address.phone ?? ''
      };
      break;
    default:
      result = {
        deliveryPhoneNumber: props.address.phone ?? ''
      };
      break;
  }
  console.log(result);
  return result;
}

f({ phoneField: 'deliveryPhoneNumber', address: { phone: '123'}});
f({ phoneField: 'pickupPhoneNumber', address: { phone: '456'}});
f({ address: { phone: '789'}});
f({ address: {}});

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

Tips for resolving the issue when Chrome is able to load the page but Postman cannot find it

I'm encountering a perplexing situation that is entirely new to me and difficult to comprehend. I find myself unable to decipher what exactly I am witnessing, leading to uncertainty about why it is occurring, not to mention the challenge of determinin ...

Integrating Octokit middleware in Next.js for enhanced functionality

Currently, I am in the process of honing my skills by creating a GitHub app. In Octokit, there is a feature called createNodeMiddleware that caught my attention. However, integrating it with next.js seems to pose some challenges. My main issue right now re ...

Could you explain the significance of the typscript parameters encapsulated within curly braces?

I'm confused about the syntax in this TypeScript code snippet. Why is the data parameter enclosed in curly braces and followed by a colon and the same data object with a type specification? Can someone explain what this means? addArrivingTruckSuggesti ...

Troubleshooting: Issues with Angular2 compatibility on Safari version 9.1.2

I am encountering an issue with running my angular2 app on Safari 9.1.2. It works fine on all higher versions of Safari as well as other browsers such as Chrome, Firefox, Opera, and Edge. However, when I try to run it on Safari 9.1.2, I receive the followi ...

Adding Relative URLs Automatically to .angular-cli.json is a simple process that can be easily

Is there a way to automatically have Angular-Cli (Angular-4) append URL's to Styles or Scripts when adding external libraries with npm install --save into .angular-cli.json? Currently, we have to manually search through the node_modules folder to fin ...

Take action once the Promise outside of the then block has been successfully completed

Presented below is the code snippet: function getPromise():Promise<any> { let p = new Promise<any>((resolve, reject) => { //some logical resolve(data); }); p.finally(()=>{ //I want do something when ou ...

Utilize TypeScript function types in React for enhanced functionality

I have made the decision to refactor a project that was originally created with vanilla JavaScript and now I want to transition it to TypeScript. One issue I am facing is how to pass a function as a type on an interface. Although I referred to the TypeScr ...

Error: Unable to locate the variable 'content' in the TypeScript code

Having an issue with my navigateToApp function. In the else condition, I am calling another function called openModalDialog(content). Unfortunately, I am encountering an error stating Cannot find name content. Can someone help me identify what is wrong h ...

Guide to separating the bytes of a number and placing them into an Uint8Array

I am looking to convert a JavaScript number into the smallest possible uint8array representation. For example : 65 535 = Uint8Array<[255,255]> (0b1111111111111111 = [0b11111111, 0b11111111]) 12 356 = Uint8Array<[48,68]> (0b0011000001000100 = [ ...

Ways to fake an interface using Jest without needing to instantiate it

While Kotlin supports this, I haven't been able to find a way to achieve the same in Jest. My problem arises from having intricate interfaces and arrays of these interfaces where specifying all attribute values is not ideal. Here's an example of ...

Surprising Media Component Found in URL Parameters within the design

Exploring the page structure of my Next.js project: events/[eventId] Within the events directory, I have a layout that is shared between both the main events page and the individual event pages(events/[eventId]). The layout includes a simple video backgro ...

sort the array based on its data type

Recently diving into typescript... I have an array that is a union of typeA[] | typeB[] but I am looking to filter based on the object's type interface TypeA { attribute1: string attribute2: string } interface TypeB { attribute3: string attri ...

Create an interface that inherits from another in MUI

My custom interface for designing themes includes various properties such as colors, border radius, navbar settings, and typography styles. interface ThemeBase { colors: { [key: string]: Color; }; borderRadius: { base: string; mobile: st ...

Leveraging the power of the Async pipe within an Angular TypeScript file

Using the async pipe in HTML involves utilizing the syntax "Products$ | async as products". But can we also access these same products in the TypeScript file? Is this possible? ...

Creating robust unit tests for Node.js applications with the help of redis-mock

I am facing an issue while trying to establish a connection with redis and save the data in redis using the redis-mock library in node-typescript, resulting in my test failing. Below is the code snippet for the redis connection: let client: RedisClientTyp ...

HTML not updating after a change in properties

My template is structured as a table where I update a column based on a button click that changes the props. Even though the props are updated, I do not see the template re-rendered. However, since I am also caching values for other rows in translatedMessa ...

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 ...

Tips for efficiently passing TypeScript constants to Vue templates without triggering excessive reactivity

I'm curious about the most efficient way to pass a constant value to a template. Currently, I am using the data property in Vue, but I believe that is better suited for state that changes over time as Vue adds event listeners to data properties. The c ...

A guide to implementing Typescript Generics in modifier functions

I am searching for a solution to properly enforce strong typing in the following scenario. I believe Typescript Generics might be the way to go here. interface Person { name: string; age: number; } const person: Person = { name: "John", ...

Packaging a NodeJS project in Visual Studio - A step-by-step guide to creating and setting up an N

In my VS2013 solution, I have a combination of NodeJS (using TypeScript) and C# class library projects connected by EdgeJS. Among the NodeJS projects, one serves as a library for a RabbitMQ bus implementation, while two are applications meant to be hosted ...