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

How can I use AJAX to send a dynamically generated PDF file?

I utilized jsPDF to create a PDF object. My goal is to send an email with a PDF attachment using AJAX, but I am facing issues in sending the file correctly. I attempted to convert it into a Blob object for transmission and later decode it to base64 in PHP ...

What is the best way to store all rows in a dynamically changing table with AngularJS?

I am facing an issue while trying to dynamically add rows for a variable that is a String array in my database. The problem is that it only saves the last value entered in the row instead of saving all of them in an array. Here is my current view code: &l ...

What is the process for determining the estimated location of a queued event within a JavaScript Engine's event queue?

Imagine a multitude of events being created and added to the event queue of a Javascript engine. Are there any techniques or recommended practices in the industry to predict the order in which a specific event will be added to the queue? Any knowledge or ...

The combination of Ajax, JavaScript, PHP/HTML, and dynamic variables

Hey there, I'm currently working on a game development project and have encountered what seems to be a scope issue. The issue arises in my js file when a player clicks on the card they want to play: It starts in my js file:</p> <pre>&l ...

Launching a new tab with a specific URL using React

I'm attempting to create a function that opens a new tab with the URL stored in item.url. The issue is, the item.url property is provided by the client, not by me. Therefore, I can't guarantee whether it begins with https:// or http://. For insta ...

The image from the local source displays correctly in the initial component without any issues, but it does not appear in its corresponding detail component

My blog has a component for displaying posts and another component for showing the details of a single post as seen below. A key point to note is that while the blog component can load and display images, the blog details component cannot. Why is this? A ...

Access the style of the first script tag using document.getElementsByTagName('script')[0].style or simply refer to the style of the document body with document.body.style

Many individuals opt for: document.getElementsByTagName('script')[0].style While others prefer: document.body.style. Are there any notable differences between the two methods? EDIT: Here's an example using the first option: ...

Using the same function in two different locations will only work for one instance

I have an AngularJS application where I am using a function called foo(bar) that takes bar as a parameter. The data for bar is retrieved from a web API, and I loop through this data using ng-repeat which works perfectly fine. <li class="list-group-item ...

Learn the steps to successfully rotate the custom icon in Material-Ui select without impacting its functionality and making sure it remains clickable

I've been trying to incorporate my own icon for the material UI select component. I successfully replaced the default icon using the "IconComponent" attribute in MU select. However, I'm facing issues with the new icon not rotating when the menu ...

Revamp HTML <font size=1-7> with the use of CSS or JavaScript

I've been developing a prototype application that incorporates a plugin utilizing the deprecated HTML feature. Can I set custom pixel sizes for each font size ranging from 1 to 7? Currently, I'm contemplating using CSS zoom/scale properties, bu ...

Issue with Vue.js where the v-if directive placed inside a v-for loop does not properly update when

I want to dynamically show elements from an array based on boolean values. However, I am facing an issue where the element does not disappear even after the boolean value changes. <li v-for="(value, index) in list"> <span> {{ value[0] }} & ...

Highchart displays results for each individual line without the use of commas

I am interested in creating a chart using Highchart with three lines. The first two lines will display data similar to [4.12, 3.34, 5.45] / [3.45, 5.45, 3.23], while the third line should be displayed without any commas, showing results like [7, 4, 2]. Can ...

Unlocking the Power of VueJS Mixins in an External JS Library

I'm currently using a 'commonLibrary.js' in my Vue application. One of the functions in this library that I find particularly useful is: var defaultDecimalRounding=3 function formatNumber(number) { if (isNaN(number.value) == tr ...

Setting up a Node.js project in your local environment and making it

I need help installing my custom project globally so I can access it from anywhere in my computer using the command line. However, I've been struggling to make it work. I attempted the following command: npm install -g . and some others that I can&ap ...

Using two loops/arrays in JavaScript to generate a rating score

I want to create a simple rating component with the following appearance: ◼◼◼◻◻ (score: 3 out of 5) Here is my JSX code snippet: var score = 3; var range = 5; { [...Array(range)].map((e, i) => ( <div className="rating-item" ...

following the history.back() function call, the subsequent codes are executed

<?php $ok_register = 0; if($ok_register != 1) { ?> <javascript type="text/javascript"> alert("1"); history.back(); </javascript> <?php } ?> <javascript type="text/javas ...

Issue with Prettier AutoFormatting in a project that combines TypeScript and JavaScript codebases

Recently, I've started incorporating TypeScript into an existing JavaScript project. The project is quite large, so I've decided to transition it to TypeScript gradually. Below is a snippet from my eslintrc.js file: module.exports = { parser: ...

Retrieving dates from a database and populating them into a jQuery UI Picker using PHP

I need to retrieve dates from the database using PHP and highlight them in a datepicker. Here is how I am attempting to accomplish this: HTML Date: <input type="text" id="datepicker"> // Static dates // An array of dates var eve ...

What is the technique for hiding the bottom tab navigator upon leaving a specific screen in React Native version 5?

In the home screen, I want only the bottom tab navigator to be visible, and then hidden until the user returns to the home screen. The example provided below is tailored for working in the App.js file, but my situation is different. const Tab = createBot ...

What is the best way to send an email with a randomly generated HTML output using a <button>?

I am currently working on a feature where a random HTML output is sent to me via email whenever a user clicks on a button on the website page. The user receives a code when they click the button, and I want that code to be emailed to my address automatical ...