I can't seem to figure it out how to solve it. It is throwing the following error: Type 'string | number' is not assignable to type 'never'
.
This is my code. Any idea or any tips are much appreciated.
interface User {
name: string
age: number
}
const user: User = {
name: "someone",
age: 20
}
const getValue = (key: keyof User) => {
return user[key]
}
const setValue = (key: keyof User, value: string | number) => {
let _user: User = Object.assign({}, user);
return _user[key] = value; <---- error is thrown here
}
CodePudding user response:
const setValue = <K extends keyof User>(key: K, value: User[K]) => {
let _user: User = Object.assign({}, user);
return _user[key] = value;
}
Then you can get perfect type checking
setValue('name', 18); // ❌
setValue('name', 'yes'); // ✅
setValue('age', '18'); // ❌
setValue('age', 18); // ✅
setValue('otherKey', 'something'); // ❌
CodePudding user response:
The way in which you have declared your types in the setValue
function results in Typescript correctly determining that there are no possible values of type _user[key]
.
Let's explore this in more detail.
The Problem
Your User
interface contains two properties, one of type string
and one of type number
.
The key
parameter of your setValue
function is of type keyof User
.
All good so far...
However, at 'compile-time' Typescript has no way to determine which key is being provided for lookup, and therefore cannot determine which specific type _user[key]
is.
What Typescript does know at compile-time, is that is either of type string
or number
. Therefore it infers the intersection type string & number
.
Which set of types are both strings and numbers?
Correct - there are no such types; Typescript therefore correctly infers that the type of _user[key]
is the empty set of types - known as the never
type.
Simple Demos
Firstly, open your favorite IDE with Typescript hinting, and define a variable with an intersection type:
let a : string & number;
Descriptively, this is saying that the the type of a
is the set of all possible types that are both string
and number
.
Typescript correctly infers that there are, in fact, no such types, and a
is therefore of type never
.
Conversely, imagine that your User
interface instead had both properties of type string
.
interface User {
name: string
age: string
}
In this case, your code would work fine, if you pass in and use only strings.
This is equivalent to saying that the type of _user[key]
is the intersection type string & string
- which of course is simply string
;
The Solution
In order to have correct type inference in your setValue
function, you need to provide a generic type, that you can make use of for both the key
and value
parameters of your setValue
function:
const setValue = <K extends keyof User>(key: K, value: User[K]) => {
const _user: User = Object.assign({}, user); // Consider also the ES6 spread operator here
_user[key] = value;
return _user;
};
Or, if you want an entirely generic setValue
function, independent of a specific type or object, your function signature could be as follows
const setValueOnObj = <T, K extends keyof T>(obj: T, key: K, value: T[K]) => [...]