DEV Community

Discussion on: Advanced TypeScript Exercises - Question 8

Collapse
 
dwjohnston profile image
David Johnston • Edited

Cool challenge.

const concatToField =
    <T extends Record<string, any>, K extends keyof T & T[K] extends string ? string : never> (obj: T, key: K, payload: string): T => {
    const prop = obj[key]; // compile error should not be here
    return { ...obj, [key]: prop.concat(payload) }; // compile error should not be here
}
// tests
const test = { fieldStr: 'text', fieldNum: 1, fieldStr2: 'text' };
concatToField(test, 'fieldStr', 'test'); // should be ok 👌
concatToField(test, 'fieldNum', 'test'); // should be error fieldNum is not string field 🛑
concatToField(test, 'notExistingField', 'test'); // should be error - no such field 🛑
concatToField(test, 'fieldStr2', 'test'); // should be ok 👌

Explanation:

T extends Record<string, any>

Pretty straight forward. The test object is a map of string keys to any values, though of course, if those values are strings, then we want to be able to apply the concatToField method to it.

 K extends keyof T & T[K] extends string ? string : never

I'll break this into two parts

K extends keyof T

By itself, this is just saying 'the value of K is going to be one of the keys of that T object'.

& T[K] extends string ? string : never

We're saying that 'is the value at T[K] of type string? If so, then the type of K is string, if not, then the type of K is never'.

The never type allows us to return compile errors when something is illogical/not allowed - in this case it is not allowed to to have a T[K] value that is not of type string.

Note:

It would be nice if we could just do

 K extends keyof T & T[K] extends string

instead of using that ternary - but this apparently is not valid typescript.

Collapse
 
macsikora profile image
Pragmatic Maciej • Edited

Hi David thank you for the answer.
But your code doesn't make errors when it should, so there are no compile time guarantees. I put in the snippet two places where the error should occur, you see there 🛑 icon. Even more the code you have provided compiles for any second argument like concatToField(test, 'anything', 'test');.

So try again! And good luck.

Collapse
 
dwjohnston profile image
David Johnston • Edited

Ah, was just missing some brackets. :/

const concatToField =
  <T extends Record<string, any>,
    K extends keyof T & (T[K] extends string ? string : never)>(obj: T, key: K, payload: string): T => {
    const prop = obj[key]; // compile error should not be here
    return { ...obj, [key]: prop.concat(payload) }; // compile error should not be here
}

Playground