Typescript: A type consisting of only one property that is of a different type

I have an interface and I want to create a type using the keys of this interface.

  • The key can be optional
  • The key must exist in that interface

For example:

interface Test {
  a: string;
  b: string;
  c: string;
  d: string | undefined;
  e: string | undefined;
}

In my new type, only one property from the keys above should be allowed. This is how I defined it:

type MyNewType<T> = {
  [key in keyof T]: T[key];
};

I used this type as MyNewType<Test>, but encountered an error.

Type '{ a: string; }' is missing the following properties from type 'MyNewType': b, c, d, e.

How can I make these properties optional?

Explanation

I am utilizing a reducer in React and invoking the dispatch function which takes an action creator as a parameter. The type Test represents the state type and MyNewType<Test> is the payload type. The action creator accepts different payloads such as { a: 'a'}, { b: 'b' }, or { d: undefined }. However, I am getting an error indicating that the payload needs to include all properties.

Answer №1

If you're looking for a specific solution instead of a generic one, consider the following:

MyNewType = { a: string }
          | { b: string }
          | { c: string }
          | { d: string | undefined }
          | { d: string | undefined };

Finding a way to do this for a generic type T is challenging.

One alternative approach could be using separate properties for key and value like this:

{
  key: "a",
  value: "a"
}

This could potentially be achieved in a generic manner:

type MyNewType<T> = {
  key: keyof T;
  value: T[keyof T];
}

However, this method allows combining any key with any value, which may not be precise.

Another option is:

type MyNewType<T, K extends keyof T> = {
  key: K;
  value: T[K];
}

This improved version introduces a new type parameter to represent the key type. For instance, it can be used as follows:

function dispatch<K extends keyof Test>(testPayload: MyNewType<Test, K>) {
  // perform actions on payload
}

dispatch({ key: "a", value: "a" });        // OK
dispatch({ key: "a", value: undefined });  // Error, because Test.a cannot be undefined
dispatch({ key: "d", value: undefined });  // OK
dispatch({ key: "x", value: undefined });  // Error, since Test doesn't have property "x"

Edit

I also attempted the following to create a type exactly as requested:

type MyNewType<T, K extends keyof T> = {
  [key: K]: T[K];
}

Nevertheless, TypeScript throws an error related to the key:

An index signature parameter type must be either 'string' or 'number'.

Answer №2

To mark optional keys, simply add a ? at the end

For example, if you want to specify that a and b are optional:

interface Test {
  a?: string;
  b?: string;
  c: string;
  d: string | undefined;
  e: string | undefined;
}

Learn more about optional properties in TypeScript from this source

Answer №3

The individual who posed this inquiry nearly nailed it, achieving a 99% accuracy rate on his own. However, there is just one tiny detail missing:)

To address this, simply make the key optional:

type MyModifiedType<T> = {
  [key in keyof T]?: T[key];
};

Answer №4

Unique solution

In my experience with TypeScript 5.0, the following approach has proven effective:

type ExtractOneProp<T> = {
    [K in keyof T]: { [_ in K]: T[K] }
}[keyof T]

Insight

For instance, applying this type transformation to:

type SampleType = ExtractOneProp<{ x: 'apple', y: 'banana'}>

would yield

type SampleType = {
    x: { x: 'apple' },
    y: { y: 'banana' },
}['x' | 'y']

resulting in

type SampleType = { x: 'apple' } | { y: 'banana' }

Application

This methodology showcases how verifying membership aids in deducing the precise type:

function accessPropertyValue(obj: ExtractOneProp<{ x: 'apple', y: 'banana' }>) {
    if ('x' in obj) {
        // obj is determined to be { x: 'apple' } due to the presence of 'x'
        return obj.x
    } else if ('y' in obj) {
        // obj is determined to be { y: 'banana' } as 'y' exists
        return obj.y
    } else {
        // obj is inferred to be { z: 'carrot' } since neither 'x' nor 'y' is included
        obj.z
    }
}

Constraints

Notably, this method mandates that at least one property from T must be present--permitting multiple properties does not trigger an error.

type SampleType = ExtractOneProp<{ x: 'apple', y: 'banana', z: 'carrot' }> // Resolves to { x: 'apple' } | { y: 'banana' } | { z: 'carrot' }

const example1: SampleType = { x: 'apple' } // Allowed
const example2: SampleType = { y: 'banana', z: 'carrot' } // Permitted: Singular property requirement not enforced
const example3: SampleType = {} // ERROR: Type '{}' cannot be assigned to 'SampleType'

Answer №5

Dealing with a similar issue, I devised a unique solution.

To address this, create an interface or type that encompasses all the properties you desire to support. However, make them optional and of type never. While you can utilize these properties, you are unable to assign any values to them.

interface BaseType {
  a?: never;
  b?: never;
  c?: never;
  d?: never;
  e?: never;
}

For each property, fashion a type by excluding the base never property and substituting it with the desired type. It is no longer optional at this point.

type AType = Omit<BaseType, "a"> & { "a": string };
type BType = Omit<BaseType, "b"> & { "b": string };
type CType = Omit<BaseType, "c"> & { "c": string };
type DType = Omit<BaseType, "d"> & { "d": string | undefined };
type EType = Omit<BaseType, "e"> & { "e": string | undefined };

Combine these individual types for single properties to form your MyNewType. Any instance of this type must align with one of the aforementioned types.

type MyNewType = AType | BType | CType | DType | EType;

The code snippets below either compile successfully or prompt TypeScript errors:

let testA : MyNewType = { "a": "test" } // A is present and is a string
let testB : MyNewType = { "b": 1 } // B is present but cannot be a number 
let testD : MyNewType = { "d": undefined } // D is present and may be undefined 
let testMultiple : MyNewType = { "a": "Some String", "b": "Another String" } // Multiple properties inclusion is not allowed

This setup has also been implemented in the Playground, providing real-time observation of the TypeScript errors.

Answer №6

I encountered a similar issue and found a solution by utilizing the concept of Partial in my code:

For instance:

interface Example {
  x: string;
  y: string;
  z: string;
  u: string | undefined;
  v: string | undefined;
}

// To address the issue, I introduced Partial
type RevisedType<T> = Partial<
  {
    [key in keyof T]: T[key];
  }
>;

Answer №7

Actually, there is a generic way to accomplish this task:

In short: enhanced by using Matt's method to make other properties optional and never have a type

    type NoProperties<T> = {
        [K in keyof T]?: never;
    }

    type SingleProperty<T> = {
        [K in keyof T]: Omit<NoProperties<T>, K> & { [P in K]: T[P] };
    }[keyof T];

Specifics:

The interface you are working with:

    interface Test {
        a: string;
        b: string;
        c: string;
        d: string | undefined;
        e: string | undefined;
    }

By using this generic type:

    type SinglePropertyIntermediate<T> = {
        [K in keyof T]: { [P in K]: T[P] };
    };

You will receive:

    {
        a: { a: string; };
        b: { b: string; };
        c: { c: string; };
        d: { d: string | undefined; };
        e: { e: string | undefined; };
    }

Now, extract the values of this object type:

    type SingleProperty<T> = SinglePropertyIntermediate<T>[keyof T]

This results in:

    { a: string; } |
    { b: string; } |
    { c: string; } |
    { d: string | undefined; } |
    { e: string | undefined; };

Answer №8

Building upon S.Young's solution

You can induce errors with multiple properties by incorporating the 'never' type

export type OneOf<T> = {
  [K in keyof T]: { [_ in K]: T[K] } & Partial<
    Record<Exclude<keyof T, K>, never>
  >;
}[keyof T];

Instructions:

type Bar = OneOf<{ x: 1, y: 2, z: 3 }>;

// Acceptable scenarios
const example1: Bar = { x: 1 }; // Valid
const example2: Bar = { y: 2 }; // Valid
const example3: Bar = { z: 3 }; // Valid

// Inappropriate scenarios
const example4: Bar = { x: 1, y: 2 }; // Error: Type '{ x: 1; y: 2; }' is not assignable to type 'Bar'
const example5: Bar = { y: 2, z: 3 }; // Error: Type '{ y: 2; z: 3; }' is not assignable to type 'Bar'
const example6: Bar = {}; // Error: Type '{}' is not assignable to type 'Bar'

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

Using Systemjs to transpile TypeScript async functions

When I manually build .ts files using the tsc tool, I notice that wrappers are generated for async/await keywords. However, I am facing an issue setting up transpile on-the-fly using SystemJS. Here is my index.htm configuration: <script src="https:// ...

Incorporating a Symbol into a Function

Reviewing the following code snippet: namespace Add { type AddType = { (x: number, y: number): number; }; const add: AddType = (x: number, y: number) => { return x + y; }; } Can a 'unique symbol' be added to the AddType lik ...

Error message in TypeScript React: Unable to assign onClick to type 'IntrinsicAttributes'

I recently came across this code for a component: Feedback.tsx import React, { useState } from 'react'; import './Feedback.css'; import FeedbackButton from './FeedbackButton'; function Feedback() { const [isOpen, setIsOpe ...

Footer missing from Tanstack React table

Library Version: "@tanstack/react-table": "^8.2.6", I have been attempting to include footers in my table without success. Despite setting static footer text with a fixed value, I am unable to render any dynamic values similar to how h ...

What is the best way to populate empty dates within an array of objects using TypeScript or JavaScript?

I am trying to populate this object with dates from today until the next 7 days. Below is my initial object: let obj = { "sessions": [{ "date": "15-05-2021" }, { "date": "16-05-2021" }, { "date": "18-05-2021" }] } The desired ...

Exploring Click Events in Angular with OpenLayers Features

After creating a map with parking points as features, I now want to implement a click function for the features. When a feature is clicked, I want to display a popup with the corresponding parking data. I've tried searching online for information on ...

Testing the Express API with MongoDB on a local machine is successful but encounters a timeout issue on CircleCI

I am facing an issue with testing a RESTful API (built with Express in TypeScript) using Jest. The test passes successfully on my local Windows machine but times out on CircleCI. .circleci/config.ylm version: 2.1 jobs: build: docker: - image: ...

Tips for accurately documenting the Props parameter in a React component with Destructuring assignment

Attempting to create JSDocs for a React component using destructuring assignment to access props: const PostComponent: React.FC<IPostComponent> = ({ title, body }) => { ... } The issue arises when trying to document multiple parameters in JSDocs ...

I'm confused about this CallbackRouteError with the provider named "credentials". Can someone please explain?

I can't seem to resolve this error after searching extensively on the web. For more information, visit [auth][error] CallbackRouteError: For more details, please visit https://errors.authjs.dev#callbackrouteerror [auth][cause]: Error at Module.callb ...

Learning how to effectively incorporate the spread operator with TypeScript's utility type `Parameters` is a valuable skill to

I have implemented a higher order function that caches the result of a function when it is called with the same parameters. This functionality makes use of the Parameters utility type to create a function with identical signature that passes arguments to t ...

Is there a way to set a default value for an Angular service provider?

Imagine an Angular Service that encapsulates the HTTP Client Module. export class HttpWrapperService { private apiKey: string; } Of course, it offers additional features that are not relevant here. Now I'm faced with the task of supplying HttpWr ...

Utilizing an Observable in an Angular 6 service to ensure proper synchronization with Okta token present in local storage

Within my application, I have implemented third-party authentication to facilitate user login and store a token in their local storage. To enhance the user experience, I am developing a service to cache profile information. This service utilizes the user&a ...

Expressjs - Error: Headers already sent to the client and cannot be set

After testing various solutions from others, I am still unable to resolve this error. My objective is to iterate through each item in the array sourced below: novel.ts export let indexList = (req: Request, res: Response) => { novel.getAllDocuments ...

There was no corresponding state found in the storage for OidcClient.readSigninResponseState using oidc-client in conjunction with React and Typescript

While browsing through related threads on the topic, I found that many of them are inactive or lack a definitive solution. In my project, I am working on a client using react and typescript. I have integrated the oidc-client module to interact with an ext ...

What is the best way to implement a Promise Function within a For loop?

Here is a function called sendEmail: public async sendEmail (log: LogMessage): Promise<void> { nodemailer.createTestAccount(async () => { return ServiceFactory.getSystemService().getNetworkPreferences().then(async (networkPreferences) => ...

Exploring the Power of DataList in Angular

I am working with a <datalist> and <select> in the following setup: Updated: Example 1: <input type="text" list="codes" [(ngModel)]="codeValue" (change)="saveCode(codeValue)"> <datalist id="codes"> <option *ngFor="let c of ...

Utilizing Typescript to Inject Generics and Retrieve the Name of an ES6 Module

I am currently working on developing a versatile repository using: Typescript ES6 Angular 1.x However, I am facing challenges in determining the correct way to inject the Entity and retrieve its module name. The main reason for needing the name: I adh ...

Performing operations on an array: What method do you favor and why? Is there a more efficient approach?

What is the most effective method for checking if an element exists in an array? Are there alternative ways to perform a boolean check? type ObjType = { name: string } let privileges: ObjType[] = [{ name: "ROLE_USER" }, { name: "ROLE_ ...

Utilizing a string as an argument in a function and dynamically assigning it as a key name in object.assign

Within my Angular 5 app written in TypeScript, I have a method in a service that requires two arguments: an event object and a string serving as the key for an object stored in the browser's web storage. This method is responsible for assigning a new ...

Tips on navigating an array to conceal specific items

In my HTML form, there is a functionality where users can click on a plus sign to reveal a list of items, and clicking on a minus sign will hide those items. The code structure is as follows: <div repeat.for="categoryGrouping of categoryDepartm ...