TypeScript: "The type is generic and can only be accessed for reading." - Error code 2862

Consider this sample JS function that requires type annotations:

const remap = (obj) => {
  const mapped = {};
  Object.keys(obj).forEach((key) => {
    mapped[key] = !!key;
  });

  return mapped;
};

I am attempting to add types using generics (in this TS playground), but I keep encountering the following error:

Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
type Mapped<T> = {
  [K in keyof T]?: boolean;
};

const remap = <T extends Record<string, unknown>>(
  obj: T
) => {
  const mapped: Mapped<T> = {};
  Object.keys(obj).forEach((key) => {
    mapped[key] = !!key; // Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
  });

  return mapped;
};

I am curious as to why TypeScript does not allow me to write to an object of a generic type, and if there might be another workaround. I expect TypeScript to recognize the type of mapped and grant me permission to write to it, but it seems to restrict that.

Would utilizing as during the return statement be my sole option?

const remapWithAs = <T extends Record<string, unknown>>(
  obj: T
) => {
  const mapped: Record<string, boolean> = {};
  Object.keys(obj).forEach((key) => {
    mapped[key] = !!key;
  });

  return mapped as Mapped<T>; // Is this my only choice?
};

Answer №1

The root cause of the error lies in the fact that Object.keys(x) is defined in the TS library to return string[] instead of something like (keyof typeof x)[]. This intentional design choice is elaborated on in this StackOverflow post. Therefore, when accessing mapped[key], you are potentially using a key of type string rather than ensuring it aligns with Mapped<T>. Consequently, directly assigning a boolean value to it might not be safe as you could be modifying a key unknown to Mapped<T>.

It's worth mentioning that TypeScript allows you to retrieve a boolean from mapped[key] despite this being technically precarious, as shown below:

Object.keys(obj).forEach((key) => {
  const test = mapped[key]; // boolean | undefined
});

This is just how TypeScript operates. Thus, the error message stating that Mapped<T> can only be indexed with string for reading purposes. Previously, the message used to prohibit indexing Mapped<T> with string entirely, but this was corrected due to inaccuracies, as documented in microsoft/TypeScript#47357.


If you assert confidently that Object.keys(obj) will yield (keyof T)[] despite TypeScript's concerns about potential additional keys, you can proceed as follows:

const remap = <T extends Record<string, unknown>>(
  initialState: T
) => {
  const mapped: Mapped<T> = {};
  (Object.keys(initialState) as (keyof T)[]).forEach(key => {
    mapped[key] = !!key; // fine
  });

  return mapped;
};

In this context, TypeScipt accepts that mapped[key] conforms to type

Mapped<T>[keyof Mapped<T>]</code — namely, <code>boolean | undefined
— and therefore permits assignments of type boolean.

Link to code playground

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

Enhancing a UMD module definition with TypeScript 2: A step-by-step guide

Currently, I am in the process of creating TypeScript definition files for two libraries that are meant to be used with the new @types approach. Both libraries adhere to the UMD pattern, allowing them to be consumed either as modules or by referencing them ...

Create a Bar Graph Using a List

Looking to generate an Angular Barchart from a JPA query in Spring: public List<PaymentTransactionsDailyFacts> findPaymentTransactionsDailyFacts(LocalDateTime start_date, LocalDateTime end_date) { String hql = "SELECT SUM(amount) AS sum_volume, ...

Navigating through a node tree and making changes to its configuration and content

Here's the input I have. Some nodes have downlines with multiple other nodes nested inside. data = [ { "user_id": "1", "username": "johndoe001", "amount": "0.00", "down ...

Error encountered during conversion to Typescript for select event and default value

When trying to set the defaultValue in a Select component, TSlint throws an error - Type 'string' is not assignable to type 'ChangeEvent<HTMLInputElement> | undefined - for the code snippet below: const App = () => { const [ mont ...

Creating XML templates in Angular 7: A comprehensive guide

How do I pass XML values in Angular 7 when the API requires this specific format of XML code? -modifydata "<datasets><dataset select=\""always\""> <replace match=\""Letter/@FName\"" value=\""Nazeeeeeeeeeeeeer\" ...

The exclude option in Nest JS middleware does not prevent the middleware from running on excluded routes

I'm having an issue with excluding certain routes from the middleware. The .exclude option doesn't seem to be working as expected, as the middleware is still being applied to the excluded routes. Here is the code for the Middleware: https://i.st ...

What is the best way to implement a hover effect on multiple rows within an HTML table using Angular?

I am currently working on developing a table preview feature to display events. I previously sought assistance here regarding positioning elements within the table and successfully resolved that issue. Applying the same principles, I am now attempting to c ...

Incorporate matTooltip dynamically into text for targeted keywords

I'm currently tackling a challenge in my personal Angular project that utilizes Angular Material. I'm struggling to find a solution for the following issue: For instance, I have a lengthy text passage like this: When you take the Dodge action, ...

Conditional Rendering with Next.js for Smaller Displays

I've implemented a custom hook to dynamically render different elements on the webpage depending on the screen size. However, I've noticed that there is a slight delay during rendering due to the useEffect hook. The conditionally rendered element ...

What is the best way to eliminate square brackets from keys within an array of objects in TypeScript?

I am currently working on a task to eliminate all square brackets from the keys in the entries field within an array of objects: data: [ {title: "Title1", entries: { 'Entry1': 333333, '[ABC]Entry2': 1234, 'Entry3' ...

Why won't my code work with a jQuery selector?

I'm struggling to retrieve the value from a dynamically generated <div> using jQuery. It seems like the syntax I'm using doesn't recognize the div with an id tag. The HTML code is stored in a variable, and below is a snippet of code w ...

Is there a way to get interpolation working outside of onInit?

In one component, I have set up a functionality to subscribe to an HTTP GET request from a service and store the response in a variable. The service contains a Subject as an observable so that it can be subscribed to in another component. However, while I ...

What is the method for utilizing Tuple elements as keys in a Mapped Type or template literal within typescript?

Is there a specific way to correctly type the following function in TypeScript? Assuming we have a function createMap() that requires: a prefix (e.g. foo) and a tuple of suffixes (e.g. ['a', 'b', 'c']) If we call createMap(& ...

By default, showcase the value of the first item in the list selected in a mat-selection-list on a separate component

In my project, I have two essential components: 1)list (which displays a list of customers) 2)detail (which shows the details of a selected customer) These components are designed to be reusable and are being utilized within another component called cus ...

Using TypeScript, effortlessly retrieve objects within React components based on their keys

I am looking for a way to dynamically choose a React component based on a key within an object import React, {useState, useEffect} from 'react' import ComponentA from '@components/ComponentA'; import ComponentB from '@components/Co ...

Retrieve the additional navigation information using Angular's `getCurrentNavigation()

I need to pass data along with the route from one component to another and retrieve it in the other component's constructor: Passing data: this.router.navigate(['/coaches/list'], { state: { updateMessage: this.processMessage }, ...

Steps for running a TypeScript project as a child process within a JavaScript project

I am facing an issue with integrating my Electron app, written mainly in JavaScript, with an Express server project built in TypeScript. When I attempt to create a child process of the TypeScript project within my electron.js file, I encounter TypeScript e ...

Firebase authentication link for email sign-in in Angularfire is invalid

Currently, I am utilizing the signInWithEmailLink wrapper from AngularFire for Firebase authentication. Despite providing a valid email address and return URL as arguments, an error is being thrown stating "Invalid email link!" without even initiating any ...

Having Issues with CDK Virtual Scrolling in Angular Material Table

Dealing with an angular material table that contains millions of records can be quite challenging. I have implemented pagination with various options such as 10, 25, 50, 100, 500, and 1000 items per page. However, when selecting the option for 1000 or all ...

Angular log out function to automatically close pop-up windows

Within my application, there is a page where users can open a popup window. When the user clicks on logout, it should close the popup window. To achieve this, I have used a static variable to store the popup window reference in the Global.ts class. public ...