Typescript: What defines a spreadable type?

I'm struggling with using object spread in Typescript functions that involve Type variables.

Is it currently achievable at all? If not, what are some succinct alternatives?

My findings with both Typescript v2.6 and v2.7-dev are as follows:
In the provided function definitions, those labeled as ok compile without issues, while the ones marked as err result in the following compiler error:

Error TS2698: Spread types may only be created from object types.

interface IMessages {
  [msgKey: string]: string; 
}

const ok1 = () => {
  type TFieldNames = "a" | "b" | "c";
  const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
  const fieldName = "XXX" as TFieldNames;
  const otherMessages = fieldErrors[fieldName]; // has type IMessages
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

const ok2 = () => {
  type TFieldNames = keyof { a: number; b: number; c: number };
  const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
  const fieldName = "XXX" as TFieldNames;
  const otherMessages = fieldErrors[fieldName]; // has type IMessages
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

const ok3 = () => {
  type TFieldNames = string;
  const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
  const fieldName = "XXX" as TFieldNames;
  const otherMessages = fieldErrors[fieldName]; // has type "any"
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

const err1 = <TFieldNames extends string>() => {
  const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
  const fieldName = "XXX" as TFieldNames;
  const otherMessages = fieldErrors[fieldName]; // has type "any"
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

const err2 = <TFields extends { [key: string]: any }>() => {
  const fieldErrors: { [field in keyof TFields]?: IMessages } = {};
  const fieldName = "XXX" as keyof TFields;
  const otherMessages = fieldErrors[fieldName]; // has type "any"
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

const err3 = <TFields extends object>() => {
  const fieldErrors: { [field in keyof TFields]?: IMessages } = {};
  const fieldName = "XXX" as keyof TFields;
  const otherMessages = fieldErrors[fieldName]; // has type "any"
  fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

Answer №1

In the TypeScript playground, if you examine the inferred type for otherMessages within err1, it appears as:

const otherMessages: { [field in TFieldNames]?: IMessages; }[TFieldNames]

To resolve this issue, you need to explicitly define the type for otherMessages:

const err1b = <TFieldNames extends string>() => {
    const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
    const fieldName = "XXX" as TFieldNames;
    const otherMessages: IMessages | undefined = fieldErrors[fieldName]; 
    fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};

It seems that TypeScript is having difficulty simplifying

{ [field in TFieldNames]?: IMessages; }[TFieldNames]
to IMessages | undefined. This could potentially be a bug in the system.

Answer №2

Expanding on artem's response: the interface mentioned is not valid within the Playground environment:

interface MessagesMap<TFieldNames extends string> {
  [field in TFieldNames]?: IMessages; // Error: A computed property name must be of type 'string', 'number', 'symbol' or 'any'
}

Hence, the err1 function would not compile from the start.

If you require an object with string-indexed properties, it is recommended to simplify the design by using a basic string type instead of a more complex type involving generics and operators like keyof or in:

interface FieldMessages {
  [field: string]: IMessages;
}

An alternative approach is to utilize a

Map<K extends string, IMessages>
object.

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

Combining multiple Observables and storing them in an array using TypeScript

I am working with two observables in my TypeScript code: The first observable is called ob_oj, and the second one is named ob_oj2. To combine these two observables, I use the following code: Observable.concat(ob_oj, ob_oj2).subscribe(res => { this.de ...

Ensuring robust type safety when using various maps and multiple enums as their keys

I am working on creating a type-safe function for retrieving values from a map. The function needs to handle specific logic in my use case, which is why I require it beyond this simple example below: enum ExampleA { A = 'A' } enum ExampleB { ...

Unexpected behavior observed in React TypeScript when using the useRef hook. TypeScript does not generate errors for incorrect ref types

I am encountering an issue with a simple react component. Even though TypeScript should throw an error, it does not when I use HTMLInputElement as the type for useRef hook and assign it to a div element. import { useRef } from "react" export de ...

Losing focus when number is changed in the input

I need to increment a value in my program based on user input. The value should always match the input number provided by the user. To achieve this, I simply set the value equal to the inputValue. Although I am able to retrieve the input value using @View ...

Utilizing Angular 2 or TypeScript to Retrieve Visitor's Location

I initially began using ServerVariables["HTTP_CF_IPCOUNTRY"] on the backend server, but it's proving to be too slow. I'm looking for an Angular or TypeScript alternative to speed things up. ...

Step-by-step guide on uploading an image file using Nextjs

I am attempting to achieve the following: Upload an image file to a Next.js application Process it using cjwbw/real-esrgan:d0ee3d708c9b911f122a4ad90046c5d26a0293b99476d697f6bb7f2e251ce2d4 Retrieve the enhanced version of the image Is there anyone who can ...

(Angular) Best methods to search for a specific string in an array

Looking to retrieve a string value from an HTML input inside an array in Angular 5 using a service file within a component. My code login.component.ts export class LoginComponent implements OnInit { userData = []; constructor(private router: Route ...

Typescript: creating index signatures for class properties

Encountering a problem with index signatures while attempting to access static and instantiated class properties dynamically. Despite researching solutions online, I have been unable to resolve the issue. The problem was replicated on a simple class: int ...

Using React with an Array of Promises in Typescript

I have a function that looks like this: function queryProposals(hash:string) { let result = api?.query.backgroundCouncil.proposalOf( hash,(data1:any)=>{ let injectedData = data1.toPrimitive().args.account as InjectedAccou ...

The process of subscribing to a service in Angular

I currently have 3 objects: - The initial component - A connection service - The secondary component When the initial component is folded/expanded, it should trigger the expansion/folding of the secondary component through the service. Within the service ...

The async pipe is failing to function properly when used with the same observable

I'm facing an issue with the async pipe in my view as it's not loading any data dynamically. On my page, I need to change observable values multiple times such as reloading the page, loading more values, and updating news content based on differ ...

Having trouble with Angular 17 router.navigate not functioning properly?

I'm facing an issue with my angular application where I have two pages - one with a form and the other with data. Upon clicking a button, I navigate to the results page using router.navigate. However, I've noticed that sometimes the navigation do ...

Using Angular2 in conjunction with the simpleheat plugin

I've been attempting to integrate the NPM plugin Simpleheat () into my Angular2 app, but unfortunately, the heatmap is not rendering even though everything appears to be set up correctly. You can find the full repository with the issue here: https:// ...

Unauthorized Access: Azure Web App and Typescript - "Access to this directory or page is restricted."

After developing a NodeJS server in Typescript, I attempted to deploy it to an Azure Web App through VS19. While the project functions correctly internally, when published to Azure, I encountered permission issues. The project was built using the "Basic A ...

Setting limits on relational data in Nest.js involves utilizing the appropriate decorators and methods to restrict

In my Nest.js application, I am working with relational data and using the TypeOrm query builder to retrieve the data. Here is an example of how I am querying the data: async find() { return this.landingSectionNameRepository.createQueryBuilder(&apo ...

Issues with extensions during the Angular 9 Ivy compilation process

Good day! After upgrading a v8 project to v9, I encountered some errors related to extensions in the compiler. These errors are not present in another project that I also recently upgraded. The extensions, which are basic for constructors and prototypes, ...

"React with Typescript - a powerful combination for

I'm facing an issue trying to create a simple list of items in my code. Adding the items manually works, but when I try to map through them it doesn't work. Apologies for any language mistakes. import './App.css' const App = () => { ...

The hook from Supabase is facing issues with proper importing

This project is a Spotify clone. The issue I'm facing is related to importing the hook. The error message reads: React Hook "useSupabaseClient" is called in function "useloadArtistImage" that is neither a React function component nor a custom React H ...

Creating a Typescript empty object to manage state in a React component

I'm currently working on creating a type for a specific object that has the possibility of being empty. Let's start by defining the state for our React class, beginning with the CharacterInventoryTabsState interface: export default interface Char ...

When navigating through the page, Google Maps will display a portion of the map corresponding to your

I recently incorporated the Angular Google map component into my project, using the following code: <google-map [options]="location?.googleMap?.mapOptions" height="100%" width="100%"> <map-marker #marker="m ...