When it comes to TypeScript, there is a limitation in assigning a value to an object key with type narrowing through the

I created a function called `hasOwnProperty` with type narrowing:

function hasOwnProperty<
  Obj extends Record<string, any>,
  Prop extends PropertyKey,
>(
  obj: Obj,
  prop: Prop,
): obj is Obj & Record<Prop, any> {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

I thought a simple function like this would work:

function addProp<T>(obj: Record<string, any>, key: string, val: any) {
  if (!hasOwnProperty(obj, key)) {
    obj[key] = val;
  }
}

However, I'm getting an error message saying

Type 'any' is not assignable to type 'never'.

TS Playground

This seems logical because the type narrowing indicates that `key` does not exist in the `obj`. However, it should not prevent me from adding the key. Am I missing something?

Edit: Moreover, the same issue occurs outside of a function as well:

const obj: Record<string, number> = {};
const key = 'key';
if (!hasOwnProperty(obj, key)) {
  obj[key] = 123;
}

Answer №1

function hasOwnPropertyCustom<
  Obj extends Record<string, any>,
  Prop extends PropertyKey,
>(
  obj: Obj,
  prop: Prop,
): obj is Obj & Record<Prop, any> {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

function addProperty<T>(obj: Record<string, any>, key: string, val: any) {
  if (!hasOwnPropertyCustom(obj, key)) {
    obj[key] = val;
  }
}

In this scenario, the variable obj is inferred as never after the condition statement. Why?

This occurs because the type of key is a string and !hasOwnPropertyCustom(obj, key) indicates that obj no longer contains any additional string keys.

How can we address this issue?

You can resolve this by adding an explicit generic for the key argument.

function addProperty<T, Prop extends string>(obj: Record<string, any>, key: Prop, val: any) {
  if (!hasOwnPropertyCustom(obj, key)) {
    obj[key] = val;
    return obj;
  }
  return obj;
}

const result = addProperty({}, 'a', 42); // Returns Record<string, any> & Record<"a", any>

To learn more about mutations in TypeScript, check out my blog Here.

@captain-yossarian's solution works well for functions where a generic for the key is possible. However, it may not work in scenarios where generics cannot be used (refer to the edit).

The following example illustrates a situation where the code will fail:

const obj: Record<string, number> = {};
const key = 'key';
if (!hasOwnPropertyCustom(obj, key)) {
  obj[key] = 123; 
}

Since you assume that the key property is optional in obj, consider using Partial:

const obj: Partial<Record<PropertyKey, number>> = {};
const key = 'key';
if (!hasOwnPropertyCustom(obj, key)) {
  obj[key] = 123; // This will work fine
}

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

What is the best way to pass the index value of a v-for loop as an argument to a computed property function in Vue?

I'm looking to pass the index value from a v-for loop (inside a path tag) as a parameter to a function stateData(index) defined in a computed property in Vue. I attempted to achieve this using v-model="stateData[index]", but an error is being displaye ...

The JQuery mobile navigation menu effortlessly appears on your screen

I am experiencing an issue with a JQuery mobile navigation that is designed for screens @979 pixels wide. The problem arises when the screen is resized to 979px - the menu pops up fully extended and covers the content of the web page. I suspect that this ...

Revamping the vertices and UVs of DecalGeometry

I am currently experimenting with ThreeJS decals. I have successfully added a stunning decal to my sphere. Below is the code snippet I am using to place the decal on my sphere. (Please disregard any custom classes mentioned in the code.) // Creating the ...

Tips for utilizing the randomColor library

Looking for guidance on incorporating the randomColor package from yarn to assign colors to various columns in a table within my project. Any examples or resources would be greatly appreciated! I am specifically working with React and Typescript. ...

Creating an object based on its type in JavaScript is a simple task

In a recent project, I found myself using the following code: function foo(type, desc) { var p = new type(desc); } Although I am not a JavaScript expert, it seems to be functioning properly in Chrome. Can anyone confirm if this is valid JavaScript? Th ...

Issue with activating a Modal through a button inside a table row on React

I'm currently working on two files: Modal.js and Users.js. Users.js features a table with an API get query linked to it, and in the last column of the table, there's a dropdown for each row that contains three buttons: View, Edit, and Delete. My ...

Errors encountered when using TypeScript with destructured variables and props not being recognized

I have a function that returns data. The object is structured with properties such as headerMenu, page, content, and footer. These properties are defined in DataProps interface. When I try to destructure the data object using the line: const { headerMenu, ...

Customizing the Class of a jQuery UI ui-autocomplete Combobox Container

Is there a way to customize the ui-autocomplete div by adding a custom class? I have several autocomplete widgets on my webpage, and I need to style their drop-downs differently. Since editing the ui-autocomplete class directly is not an option, I am wor ...

What is the reason for receiving an error with one loop style while the other does not encounter any issues?

Introduction: Utilizing TypeScript and node-pg (Postgres for Node), I am populating an array of promises and then executing them all using Promise.all(). While pushing queries into an array during iteration over a set of numbers, an error occurs when the ...

Is the dragging behavior of a rotated image different than that of the original image when using CSS rotation?

While working on a CSS grid to showcase images rotated at 60 degrees for a diagonal view, I encountered an issue. I wanted users to have the ability to drag and drop images within the grid, but when they drag an image, it moves as if it weren't rotate ...

Creating an Interactive Table Using HTML, JavaScript, and PHP?

After reviewing this project, I am looking to customize the js function in relation to my own table. The key focus is on the following code: $("#employee_table tr:last").after("<tr id='row"+$rowno+"'><td><input type='text&apo ...

Executing JavaScript - Triggering an 'onClick' event within a For loop to dynamically load multiple hyperlinks

I am currently working on creating a listview using JSON data. However, when I call an 'onclick' function from a For loop, the link opens in a new window and loads three URLs into the browser's URL input. Is there a way to modify the code be ...

Obtain identical socket event in two separate useEffect hooks

I am facing an issue where I want to access the same event in two different useEffect functions on the same page. Despite my attempts, it doesn't seem to work as expected. Below is what I have tried so far. I'm wondering if it's possible to ...

Can Next.js 13 components maximize performance by utilizing server and client components simultaneously? What is the best approach to achieve this?

Introduction Currently, I am exploring server and client components in Next.js 13 and looking to incorporate them into my project. One of the key features is a container component that utilizes react-intersection-observer to track which section is visible ...

A ReactJS Error occurred: {error: 400, reason: "Failed match", message: "Failed match [400]", errorType: "Meteor.Error"}

I encountered an issue while attempting to send form data to the server when clicking on the Next Button in a Wizard Form. The error that occurs is related to an "Undefined User" warning displayed in the Console during Step 1 of the form submission: " { ...

Update the function's argument type signature if the current argument is a function with properties

Looking for input on a potential title change, but for now, here are the details of my specific use case: I'm currently developing a library that facilitates executing methods remotely and accessing properties across serialized boundaries like those ...

What is the best way to forward a file upload request from a Next.js API to another API?

Trying to crop an image within a Next.js application, then sending it through an API route within the app before reaching an external API endpoint is proving to be a challenge. The process works fine without going through the API route, but once that step ...

I am currently struggling with a Typescript issue that I have consulted with several individuals about. While many have found a solution by upgrading their version, unfortunately, it

Error message located in D:/.../../node_modules/@reduxjs/toolkit/dist/configureStore.d.ts TypeScript error in D:/.../.../node_modules/@reduxjs/toolkit/dist/configureStore.d.ts(1,13): Expecting '=', TS1005 1 | import type { Reducer, ReducersMapO ...

Should I release an Aurelia component on NPM?

Our team has developed a compact Aurelia application and now we are looking to seamlessly incorporate it into a larger codebase. One possible scenario is distributing the Aurelia app on NPM to allow other projects to easily integrate our code. What steps ...

Customizing Material UI Components: Implementing the onChange Method

I've been working with the materia kit react template from creative-tim: However, I noticed that the customerInput component in this template lacks an onChange method. Does anyone have a solution for handling user inputs using this specific template? ...