I've done all 3, for different use cases. Last one in particular is nice if you really just want TS to shut up and let you handle it. Sometimes there's some fancy logic in the function above to check if it'll work, but you still want autocomplete on the results in one form or another, and these might not agree with the object type, and typescript doesn't quite narrow things down as much as I'd like it to. Then as keyof typeof obj is a real life saver. You could do it in a safer manner, but it would almost always involve very abstract types and a lot of headache. Definitely a sharp knife that one, that's a great analogy.
Something confusing but actually kind of necessary (due to how JS works) that I think you could have mentioned here is how Record is still technically creating strings that point to type T. It's why I think for dynamic options, Map can be a little nicer to work with in Typescript.
@@jaroslavoswald7566 The idea is any time you go to take a dynamic record and turn it into something useful for calculating something, it gets deserialized to a string. Object.keys({1: "hello"}) turns into ["1"]
@@jaroslavoswald7566I think he means in js if use number as index it gets converted to string. So the type of key will be string | number in the case he refers to. But can't check now.
Solution #4 is ugly because of problems with conditional types as return types but you can do: ```ts const myObj = { a: 12, b: 'hello' }; type Unlocked = T extends keyof typeof myObj ? typeof myObj[T] : undefined; const access = (key: T): Unlocked => myObj[key] as Unlocked ; ``` And then you get ```ts const something = access('a'); // ^? number const somethingElse = access('b'); // ^? string ```
4th option: `// @ts-expect-error` above the line. It's almost explicit sign for people reading the code that the developer just wanted typescript to shut up. It might lead to leaner code tbh. As long as such stuff is contained in small edge-case functions. It's probably fine :)
Honestly, this kind of example shows how absolutely batshit insane TS is for newcomers. Its error messages so very often don't make a shred of sense. They really need to either make TS better (they are continually doing so anyway) or make the error messages more sensible. I'm almost never reading TS errors anymore. It goes straight into ChatGPT and then it makes sense about 5 seconds later.
TS is an attempt to fix a crappy language but it's so crappy there's always gonna be these weird quirks and bugs. What needs to be done is getting rid of JS and let us use a proper language in the browser. If i could use c# both back and front end I'd be so happy.
and without TS you will get noticed at runtime with plain JS about such string|undefined errors, especially in production. TS tries to help you when writing code.
@@Delphi80SVK I've worked with vanilla JavaScript for over 16 years. Coding conventions, naming conventions, JSDoc, etc. all did the trick perfectly fine. TypeScript also COSTS a lot of time trying to figure out its stupidities (like pointed out in this video), and trying to figure out some genius's 5 nested single-letter generics.
One “gotcha” of keyof is that you can not use keyof Whatever if Whatever[key] is a private member. Extremely frustrating in certain scenarios. Another is if you are using a mapped types and then the corresponding keyof Type as a function parameter. “Type string cannot be used to access type string | number | Symbol” This one is particularly perplexing.
I just used the last one today and I did not come up with another solution. In short, it could be very useful when typescript can not infer the type correctly, when using Object.keys() or some array method (filter(), etc.). So my case is that I have a constant object (defined using as const) and its key and value are strict type (let's say, key is "a | b", value is "1| 2"). I would like to generate an array of object, like this {label: "a" | "b", value: 1 | 2}[], to be used as the option for a select component. The easiest way I can think of is to use "Object.keys()" or "Object.entities()" and then map through it to get the array. The issue then starts, typescript will NOT infer the type of the key to be "a" | "b" but string (the value will be inferred as 1 | 2 though). And because of the string, I can not use it as a index to get the value and or make the new array to have the strict type I would like to have. The type would be {label: string, value: 1 | 2}[] which is far from perfect. So I have to cast the type of the key to be "a" | "b" and it will only be this because the whole object is readonly and nothing can be changed.
For this situations I have always in utils my own `keys` or `entries` functions. But they are generic and they are correctly infering key as string literals. It loooks like this: const keys = (obj: T) => { return Object.keys(obj) as Array } const result = keys({ a: 1, b: 2 }) // ^? const result: ("a" | "b")[]
When will you ever use option 3 instead of option 1? I'm not quite sure how the `for loop` you mentioned could lead you to use 3. after all, the iterator should have an array of all possible types of keys.
Why? Most of tech influencer don't really have extensive experience. Also, Vercel is overrated as is Next. There are far better, easer and more intuitive technologies out there.
The guys from JetBrains are really sharp, too. It depends on who you get from FAANG. Those are big companies with a lot of variation across the many engineering teams.
This is something I've found extremely annoying when using string flags where I want undefined, null, or empty string to be an option and then I want to index an object with these strings. TS almost always makes me either lie to it (and thus force me to always remember my lie) or create a "dummy" key in my object.
The actual solution is none of the above; the actual issue here is that we have a requirement here to have non-compile-time type-checking. The real solution, without pulling an external library like zod, is to combine runtime-type-checking with compile-time-type-casting. For example: ```typescript const access = (key: string) => { if (!Object.keys(myObj).includes(key)) { throw new Error(`Invalid key for myObj: ${key}`); } return myObj[key as keyof typeof myObj]; }; ``` That said, as another commenter said, this is some batshit insane stuff for typescript. I know there's projects out there like zod and ts-reflect, but I really wish the TS devs would add to the core of typescript, optional runtime type structures. Something like this: ```typescript import "@total-typescript/ts-reset/array-includes"; const access = (keyString: string) => { const key = typescript.toKeyType(keyString); // ^?: "a" | "b" return myObj[key] } ``` They really seem against this idea but it's a massive, glaring hole in the whole system. Hell, even if we had a single typescript runtime function that just dumps the type information as a data structure, that would go a long way, then we could have a library like `clark` or something: ```typescript const myObjTypedef = typescript.dump(); assert.intersects(myObjTypedef, { kind: "Object", keys: [ "a", "b" ] }) const access = (key: string) => { return clark(myObj).indexPropertyViaString(key) } ``` I dunno. It's a major problem with typescript that gets swept over too often
Hi Matt Pocock I'm stuck with creating a type which determines the args types of callback function so while calling the callback function it should give type error while passing wrong arguments for eg : const main=(cb)=>{ cb("abc", "232"); // not giving type mismatch error } function cb(a: number, b: number){ return a+b } Thanks in advance
Check out the “Parameters” utility type. You can use it with a spread operator (ie something like function(…args: Parameters[]) or you can even index the different args of the function Parameters[0] to get a specific argument
Isn't there a funky way to define `myObj` type as a Record intersected with the `typeof myObj`? For example with a mapped type? So that any string is a legal key returning some default value type, but `a` and `b` keys have a particular value type.
@@mattpocockuk thx! so **until that constraint is dropped**, my idea is only possible for a case where the type of the properties of `myObj` is the same as the value type defined in Record... which is basically just a Record 😄
@@mattpocockuk `{ [k: string]: string } & { a: number, b: number }` is valid TS. the properties `a` and `b` will be of type number and any supernumerary property will be of type `string`
Why couldn't I have seem this 4 hours ago when I run into this problem 😂 anyway solved it using as constant on the object which seems to work, sure it will bite me at some point, but it's only a next13 pocket. Many thanks love your videos
Hey Matt, thanks for your videos, it’s so simple explaining of so difficult understanding topics. BTW It will be cool to know your point of view on the following case: there are interfaces - one is base, and others are extended from it. Also, we have classes that implement all the interfaces. What is your suggestion to implement some generic method (typeOf) in the base class via which we can check what the actual interface of this instance is? I’m sure it must be interesting to most TS developers. Thanks in advance! interface IBaseComponent interface IComponent1 extends IBaseComponent interface IComponent2 extends IBaseComponent abstract class BaseComponent implements IBaseComponent { public abstract typeOf():this is T } class Component1 extends BaseComponent class Component2 extends BaseComponent //___________________________________ const factory = new ComponentFactory(); const component1 = factory.newComponent1(); // component1: IBaseComponent const component2 = factory.newComponent2(); // component2: IBaseComponent component1.typeOf // false component1.typeOf // true && component1 is IComponent1
Love your videos, really great work. Could you possibly do a tutorial on how to create polymorphic types for a React box component. It seems since the release of Typescript v5, the old ways for handling these types don't work anymore. I've been banging my head against the keyboard for days trying to figure it out. Thanks for the content! :D
@@mattpocockuk Say you recieve an object from a server that you need to show your end user. And the types could be Dynamic Text, Image, Video, TH-cam embed, PDF document, Audio etc. You could solve it with a switch case but that gets really messy really fast. Are there other ways you could solve it?
I’d take the opposite stance to this video and actually argue that dynamic objects should be this hard. I’d probably implement a simple `key in obj ? obj[key] : undefined` - but I think TS is correct in forcing you to make a decision, and future travellers will be thankful you were explicit about expectations
In the end all my ts problems are solved using "as any" and tests. Typescript does not replace tests and the mental effort to make all types correctly it's not worth most of the time
Is there a way to (un)safely access the object with a unknown key, and to check if the result is defined? Like, ```if(!myObj.at(key)){return;} continue```. In other words, I know that I don't know if my key is a keyof my object, so I want to check to see if my object is defined at this key. Disabling 'noUncheckedIndexedAccess' or type-casting can't really be the only option, or?
Unpopular opinion: I think AI or Copilot in the editor should handle TS. Developers shouldn't sweat over these types. 😅 It feels like we spend more time than the actual perks we get. Anyone else feel this way? And oh, anyone know a killer VSCode extension for this type madness? 👀🔧
"Technically you could index into my object with D"
That's a pick up line I haven't heard before
I've done all 3, for different use cases. Last one in particular is nice if you really just want TS to shut up and let you handle it. Sometimes there's some fancy logic in the function above to check if it'll work, but you still want autocomplete on the results in one form or another, and these might not agree with the object type, and typescript doesn't quite narrow things down as much as I'd like it to. Then as keyof typeof obj is a real life saver. You could do it in a safer manner, but it would almost always involve very abstract types and a lot of headache.
Definitely a sharp knife that one, that's a great analogy.
Sometimes putting `// @ts-expect-ignore` above the line is nice as well ;) If you're casting, you're ignoring type-safety anyway. Use it wisely!
I Generally avoid using the keyword as unless it's some communication with the external world like db or api
Something confusing but actually kind of necessary (due to how JS works) that I think you could have mentioned here is how Record is still technically creating strings that point to type T.
It's why I think for dynamic options, Map can be a little nicer to work with in Typescript.
Can you elaborate more? I tried to read that statement few time, but it doesnt make sense to me. How is `Record` creating string that point to T?
@@jaroslavoswald7566 The idea is any time you go to take a dynamic record and turn it into something useful for calculating something, it gets deserialized to a string.
Object.keys({1: "hello"})
turns into
["1"]
@@jaroslavoswald7566I think he means in js if use number as index it gets converted to string. So the type of key will be string | number in the case he refers to. But can't check now.
Solution #4 is ugly because of problems with conditional types as return types but you can do:
```ts
const myObj = { a: 12, b: 'hello' };
type Unlocked = T extends keyof typeof myObj ? typeof myObj[T] : undefined;
const access = (key: T): Unlocked =>
myObj[key] as Unlocked
;
```
And then you get
```ts
const something = access('a');
// ^? number
const somethingElse = access('b');
// ^? string
```
4th option: `// @ts-expect-error` above the line. It's almost explicit sign for people reading the code that the developer just wanted typescript to shut up. It might lead to leaner code tbh. As long as such stuff is contained in small edge-case functions. It's probably fine :)
That'll leak an 'any' into the rest of the codebase! Best avoided.
Honestly, this kind of example shows how absolutely batshit insane TS is for newcomers. Its error messages so very often don't make a shred of sense. They really need to either make TS better (they are continually doing so anyway) or make the error messages more sensible.
I'm almost never reading TS errors anymore. It goes straight into ChatGPT and then it makes sense about 5 seconds later.
TS is an attempt to fix a crappy language but it's so crappy there's always gonna be these weird quirks and bugs. What needs to be done is getting rid of JS and let us use a proper language in the browser. If i could use c# both back and front end I'd be so happy.
@@disinfect777 I have no issues with JavaScript, and I don't need TypeScript. The only reason I use it is because companies insist on it.
and without TS you will get noticed at runtime with plain JS about such string|undefined errors, especially in production. TS tries to help you when writing code.
@@Delphi80SVK I've worked with vanilla JavaScript for over 16 years. Coding conventions, naming conventions, JSDoc, etc. all did the trick perfectly fine.
TypeScript also COSTS a lot of time trying to figure out its stupidities (like pointed out in this video), and trying to figure out some genius's 5 nested single-letter generics.
@@mahadevovnlyes some people overtype things
One “gotcha” of keyof is that you can not use keyof Whatever if Whatever[key] is a private member. Extremely frustrating in certain scenarios.
Another is if you are using a mapped types and then the corresponding keyof Type as a function parameter. “Type string cannot be used to access type string | number | Symbol”
This one is particularly perplexing.
What about writing `Record` instead? Doing to much or too little is always cause trouble and confusion.
Anybody else from Python or PHP saying "wow, I wish we had something like TypeScript in our world"?
I just used the last one today and I did not come up with another solution. In short, it could be very useful when typescript can not infer the type correctly, when using Object.keys() or some array method (filter(), etc.).
So my case is that I have a constant object (defined using as const) and its key and value are strict type (let's say, key is "a | b", value is "1| 2"). I would like to generate an array of object, like this {label: "a" | "b", value: 1 | 2}[], to be used as the option for a select component. The easiest way I can think of is to use "Object.keys()" or "Object.entities()" and then map through it to get the array. The issue then starts, typescript will NOT infer the type of the key to be "a" | "b" but string (the value will be inferred as 1 | 2 though). And because of the string, I can not use it as a index to get the value and or make the new array to have the strict type I would like to have. The type would be {label: string, value: 1 | 2}[] which is far from perfect. So I have to cast the type of the key to be "a" | "b" and it will only be this because the whole object is readonly and nothing can be changed.
For this situations I have always in utils my own `keys` or `entries` functions. But they are generic and they are correctly infering key as string literals. It loooks like this:
const keys = (obj: T) => {
return Object.keys(obj) as Array
}
const result = keys({ a: 1, b: 2 })
// ^? const result: ("a" | "b")[]
When will you ever use option 3 instead of option 1? I'm not quite sure how the `for loop` you mentioned could lead you to use 3. after all, the iterator should have an array of all possible types of keys.
2:56 A quick question: Will there ever be a time when FOR..IN loop will be typed?
No - TypeScript would have to support exact object types, which I don't think it will ever do.
If you ask me, working at Vercel is higher than FAANG.
Why? Most of tech influencer don't really have extensive experience. Also, Vercel is overrated as is Next. There are far better, easer and more intuitive technologies out there.
The guys from JetBrains are really sharp, too. It depends on who you get from FAANG. Those are big companies with a lot of variation across the many engineering teams.
@@brokula1312examples of such technologies?
@@brokula1312such as?
This is something I've found extremely annoying when using string flags where I want undefined, null, or empty string to be an option and then I want to index an object with these strings. TS almost always makes me either lie to it (and thus force me to always remember my lie) or create a "dummy" key in my object.
The actual solution is none of the above; the actual issue here is that we have a requirement here to have non-compile-time type-checking. The real solution, without pulling an external library like zod, is to combine runtime-type-checking with compile-time-type-casting. For example:
```typescript
const access = (key: string) => {
if (!Object.keys(myObj).includes(key)) {
throw new Error(`Invalid key for myObj: ${key}`);
}
return myObj[key as keyof typeof myObj];
};
```
That said, as another commenter said, this is some batshit insane stuff for typescript. I know there's projects out there like zod and ts-reflect, but I really wish the TS devs would add to the core of typescript, optional runtime type structures. Something like this:
```typescript
import "@total-typescript/ts-reset/array-includes";
const access = (keyString: string) => {
const key = typescript.toKeyType(keyString);
// ^?: "a" | "b"
return myObj[key]
}
```
They really seem against this idea but it's a massive, glaring hole in the whole system. Hell, even if we had a single typescript runtime function that just dumps the type information as a data structure, that would go a long way, then we could have a library like `clark` or something:
```typescript
const myObjTypedef = typescript.dump();
assert.intersects(myObjTypedef, {
kind: "Object",
keys: [ "a", "b" ]
})
const access = (key: string) => {
return clark(myObj).indexPropertyViaString(key)
}
```
I dunno. It's a major problem with typescript that gets swept over too often
Hi Matt Pocock
I'm stuck with creating a type which determines the args types of callback function
so while calling the callback function it should give type error while passing wrong arguments
for eg :
const main=(cb)=>{
cb("abc", "232"); // not giving type mismatch error
}
function cb(a: number, b: number){
return a+b
}
Thanks in advance
mattpocock.com/discord is the best spot for this stuff
Check out the “Parameters” utility type. You can use it with a spread operator (ie something like function(…args: Parameters[]) or you can even index the different args of the function Parameters[0] to get a specific argument
Isn't there a funky way to define `myObj` type as a Record intersected with the `typeof myObj`? For example with a mapped type? So that any string is a legal key returning some default value type, but `a` and `b` keys have a particular value type.
It's actually pretty hard to do this, and might be impossible - because the index signature HAS to match up with any defined types.
@@mattpocockuk thx! so **until that constraint is dropped**, my idea is only possible for a case where the type of the properties of `myObj` is the same as the value type defined in Record... which is basically just a Record 😄
@@Endrju219 Exactly!
Until you can exclude literal types from their respective general types, I don't see how you would be able to do this, tbh
@@mattpocockuk `{ [k: string]: string } & { a: number, b: number }` is valid TS. the properties `a` and `b` will be of type number and any supernumerary property will be of type `string`
I would've do this:
const obj = {
a: 1,
b: 2,
}
function access(key: K): typeof obj[K & keyof typeof obj] | ([Exclude] extends [never] ? never : undefined) {
return (obj as any)[key]
}
access("a") // number
access("b") // number
access("c") // undefined
declare var str: string;
access(str) // number | undefined
declare var abx: 'a' | 'b' | 'x'
access(abx) // number | undefined
Why the wrap exclude in a array/tuple?
@@lengors7327 because you can't distribute never. It's the standard way to check never.
keyword "as" should have an alias "trustmebro"
Why couldn't I have seem this 4 hours ago when I run into this problem 😂 anyway solved it using as constant on the object which seems to work, sure it will bite me at some point, but it's only a next13 pocket. Many thanks love your videos
Yet another BANGER
Thank you very much.
Great work.
Hey Matt, thanks for your videos, it’s so simple explaining of so difficult understanding topics. BTW It will be cool to know your point of view on the following case: there are interfaces - one is base, and others are extended from it. Also, we have classes that implement all the interfaces. What is your suggestion to implement some generic method (typeOf) in the base class via which we can check what the actual interface of this instance is? I’m sure it must be interesting to most TS developers. Thanks in advance!
interface IBaseComponent
interface IComponent1 extends IBaseComponent
interface IComponent2 extends IBaseComponent
abstract class BaseComponent implements IBaseComponent
{
public abstract typeOf():this is T
}
class Component1 extends BaseComponent
class Component2 extends BaseComponent
//___________________________________
const factory = new ComponentFactory();
const component1 = factory.newComponent1(); // component1: IBaseComponent
const component2 = factory.newComponent2(); // component2: IBaseComponent
component1.typeOf // false
component1.typeOf // true && component1 is IComponent1
Interfaces disappear at runtime, so this is unlikely to work
Love your videos, really great work. Could you possibly do a tutorial on how to create polymorphic types for a React box component. It seems since the release of Typescript v5, the old ways for handling these types don't work anymore. I've been banging my head against the keyboard for days trying to figure it out. Thanks for the content! :D
This is coming in my Advanced React workshop! Though a YT video here would be great too.
Amazing! Can't wait for this.
How are those solutions written? Is it just markdown in a code editor?
Yeah it's markdown in a CMS
What about dynamic components in react with typescript?
What kinds of dynamism are you thinking about?
@@mattpocockuk Say you recieve an object from a server that you need to show your end user. And the types could be Dynamic Text, Image, Video, TH-cam embed, PDF document, Audio etc. You could solve it with a switch case but that gets really messy really fast. Are there other ways you could solve it?
@@Tazzad A big discriminated union
"TS things it's a number"... **Thinks**
at 2:55
Internet of Thinks!
why not use as const?
Because it's supposed to be dynamic - and as const is for creating static objects.
as const?
Check out my other video on it
I’d take the opposite stance to this video and actually argue that dynamic objects should be this hard.
I’d probably implement a simple `key in obj ? obj[key] : undefined` - but I think TS is correct in forcing you to make a decision, and future travellers will be thankful you were explicit about expectations
I thing you made a spelling mistake in the last example
or - in this example - just
const myMap = new Map([
["a", 1],
["b", 1],
])
In the end all my ts problems are solved using "as any" and tests. Typescript does not replace tests and the mental effort to make all types correctly it's not worth most of the time
Is there a way to (un)safely access the object with a unknown key, and to check if the result is defined? Like, ```if(!myObj.at(key)){return;} continue```. In other words, I know that I don't know if my key is a keyof my object, so I want to check to see if my object is defined at this key. Disabling 'noUncheckedIndexedAccess' or type-casting can't really be the only option, or?
Typo 😉 in the comment of your last example: "TS things..."
typecool
Unpopular opinion: I think AI or Copilot in the editor should handle TS. Developers shouldn't sweat over these types. 😅 It feels like we spend more time than the actual perks we get. Anyone else feel this way? And oh, anyone know a killer VSCode extension for this type madness? 👀🔧
I think it is waste of time. I would rather spend the time on making sure the features for business requirement.
Good luck
What is a waste of time?
some of these solutions make the code work for years, the line between a simple yet brilliant solution and overenginnering things is thin I guess