I provided this suggestion back in October in the same repo as you list in your description as a PR (which is still open as of writing this comment). Kinda sad that you didn't even wrote a message, but glad you found this yourself.
No hard feelings. Looking at it now, I can see that the PR description didn't really make clear what I was expecting. I can also see how one can simply check a PR since the project is more of a personal collection than one open for contributions. I'm just glad that a bunch of people can finally learn about this cool feature 🙌
For the folks who use typescript-eslint rules that error/warn you this when trying to use {} as a type: The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`. - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. - If you want a type meaning "any object", you probably want `object` instead. - If you want a type meaning "any value", you probably want `unknown` instead. You can use NonNullable to get the same type without linting errors
10 дней назад
Great explanation ever; I also understood abstract type definition!! Thank you v.m!!
For this problem, I've used function overloads in the past, but this looks like it could be a nicer pattern in comparison! Although I do worry the `& {}` syntax could be a little confusing, whereas fn overloads are quite explicit/clear? ``` type Language = 'JavaScript' | 'TypeScript'; function setLanguage(arg: string): void function setLanguage(arg: Language): void function setLanguage(arg: Language | string): void {} setLanguage('scr') ```
In general it called brand types. I prefer using another method: you define NonCollapsableWithPrimitive type that accepts generic primitive like string, number etc. And you just chain this with your union type like this: type Language = 'JS' | 'TS' | NonCollapsableWithPrimitive I think it more readable
Great example, thanks for showing it. I have another problem similar to this one. Imagine you have an object that can have a set of known keys with their types but could also have other keys, all of another type. Say, type Example = { empty: boolean, [key: string]: string }. How could we type that in TS to get proper auto completion and no compilation errors?
If the requirement truely is that any other key can be a string, but `empty` must be a boolean, then I don't think this is possible from an "input type" persepctive. I'd suggest changing the function to accept two arguments, or nest the `any other key` inside another key. For example: `type { empty: boolean, data: Record }` However, if this is not an option for whatever reason, you could achieve something similar by creating an impossible type and using a helper fn to create the impossible type in a type-safe way (by accepting 2 args) ```ts type SpecialRecord = Record & Record; // Note, `Exclude` does nothing. It's the same as `string`. I just prefer it because the intent is more clear to humans. eg. // type SpecialRecord = Record & Record; function run(input: SpecialRecord) { const empty = input.empty; // Will correctly show as boolean const anyKey = input.anyKey; // Will correctly show as string // Your logic here } // helper fn to create the impossible type function createRecord(specialObj: Record, { empty, ...restObj }: Record) { return { ...restObj, ...specialObj, } as SpecialRecord; } run(createRecord({ empty: false, foo: 'foo', // This will error }, { foo: 'test', bar: 'bar', empty: 'true', // This will be allowed type-wise, but will be ignored baz: true, // This will error (comment out the first error to see) })) ```
@@ben-sterling Thanks for the example. The reason I'm asking is quite trivial: if you use a Map from Java in an API (swagger), and if that Map is of type then the resulting response is just that - a record that has the field `empty` of type Boolean and anything else of type String. Frankly, I find this case more and more common and if TS would allow for such typing to be made easily, it would make a lot of things much safer.
I'm not 100% sure on this being accurate, but my understanding is this: A union of | is automatically widened by TS into being just -- this is why the any type "corrupts" everything else in a union, because it is the widest type present. The "empty object" type {} actually represents "any non-nullish value" -- in other words, any type that is not null or undefined. By defining an intersection of string & {}, you effectively create a type that is "as wide as" the string type, but does not inherently widen -- therefore, when you create a union between | , the result is you can have or any string as a value.
I need exactly help on this one please.In a react I have a AriaRole type that also has this intersection type of string and object. Now I want to create a new type from the AriaRole without this intersection type. I have tried many ways but still couldn't able to omit or Exclude this intersection type. Do you have any solution for this ?
Do you have a video where you talk about how to make ts flag errors for missing properties within a complex object? For example: export interface normalForm { rules?: { basicValidation?: rules; yupValidation?: Yup.Schema; }; toInput?: DynamicTextFieldProps; component?: ReactNode; grid?: RegularBreakpoints; } The toInput property, when I do not set the required properties, does not give me an error in ts, even when setting the toInput prop as required.
TBH this snippet is lacking a lot of context that would explain why you don't get a TS error, but generally speaking, nested object properties don't have an impact on TS being able to accurately report validation errors unless you're doing something ludicrous or over-using the any type If you had explanations of things like the Yup.Schema, DyanmicTextFieldProps, TextFieldPropsType, etc etc...
pls, make a video like this, but for objects that have certain keys, but isnt restricted for those keys. something like: type Type = { key1: type, key2: type, [key in string]: any }
If you're talking about the JS native Map object, then the only real way to "know" the keys/values are to build your own type over it that either takes in a shape type that you can extract the "keyof" from, or utilizes a builder-style type to provide known key names to a final object
I provided this suggestion back in October in the same repo as you list in your description as a PR (which is still open as of writing this comment). Kinda sad that you didn't even wrote a message, but glad you found this yourself.
I‘m really sorry. I completely missed that. I will look into it an merge it ASAP. Thanks for bringing it up.
He must have thought it was a lenient suggestion
I see what you did there 😁
I merged it 🙏
No hard feelings. Looking at it now, I can see that the PR description didn't really make clear what I was expecting. I can also see how one can simply check a PR since the project is more of a personal collection than one open for contributions. I'm just glad that a bunch of people can finally learn about this cool feature 🙌
For the folks who use typescript-eslint rules that error/warn you this when trying to use {} as a type:
The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`.
- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
- If you want a type meaning "any object", you probably want `object` instead.
- If you want a type meaning "any value", you probably want `unknown` instead.
You can use NonNullable to get the same type without linting errors
Great explanation ever; I also understood abstract type definition!! Thank you v.m!!
Glad it was helpful!
The other solution for this Auto completion is omiting the exact type from the string.
type ComplexLanguage = Language & Omit
For this problem, I've used function overloads in the past, but this looks like it could be a nicer pattern in comparison!
Although I do worry the `& {}` syntax could be a little confusing, whereas fn overloads are quite explicit/clear?
```
type Language = 'JavaScript' | 'TypeScript';
function setLanguage(arg: string): void
function setLanguage(arg: Language): void
function setLanguage(arg: Language | string): void {}
setLanguage('scr')
```
Maybe it‘s a bit hard at first. But we could wrap this „string | {}“ into it‘s own type
In general it called brand types. I prefer using another method: you define NonCollapsableWithPrimitive type that accepts generic primitive like string, number etc. And you just chain this with your union type like this:
type Language = 'JS' | 'TS' | NonCollapsableWithPrimitive
I think it more readable
Also a great idea 👍
I need those videos.
I got you covered 😁
1:52 what extension did you use for that
It‘s called „twoslash queries“
Great example, thanks for showing it. I have another problem similar to this one. Imagine you have an object that can have a set of known keys with their types but could also have other keys, all of another type. Say, type Example = { empty: boolean, [key: string]: string }. How could we type that in TS to get proper auto completion and no compilation errors?
I will look into it and come back to you 🤘
If the requirement truely is that any other key can be a string, but `empty` must be a boolean, then I don't think this is possible from an "input type" persepctive.
I'd suggest changing the function to accept two arguments, or nest the `any other key` inside another key. For example:
`type { empty: boolean, data: Record }`
However, if this is not an option for whatever reason, you could achieve something similar by creating an impossible type and using a helper fn to create the impossible type in a type-safe way (by accepting 2 args)
```ts
type SpecialRecord = Record & Record;
// Note, `Exclude` does nothing. It's the same as `string`. I just prefer it because the intent is more clear to humans. eg.
// type SpecialRecord = Record & Record;
function run(input: SpecialRecord) {
const empty = input.empty; // Will correctly show as boolean
const anyKey = input.anyKey; // Will correctly show as string
// Your logic here
}
// helper fn to create the impossible type
function createRecord(specialObj: Record, { empty, ...restObj }: Record) {
return {
...restObj,
...specialObj,
} as SpecialRecord;
}
run(createRecord({
empty: false,
foo: 'foo', // This will error
}, {
foo: 'test',
bar: 'bar',
empty: 'true', // This will be allowed type-wise, but will be ignored
baz: true, // This will error (comment out the first error to see)
}))
```
@@ben-sterling Thanks for the example. The reason I'm asking is quite trivial: if you use a Map from Java in an API (swagger), and if that Map is of type then the resulting response is just that - a record that has the field `empty` of type Boolean and anything else of type String. Frankly, I find this case more and more common and if TS would allow for such typing to be made easily, it would make a lot of things much safer.
Need more depth explanation abt *string & {}*
I'm not 100% sure on this being accurate, but my understanding is this:
A union of | is automatically widened by TS into being just -- this is why the any type "corrupts" everything else in a union, because it is the widest type present.
The "empty object" type {} actually represents "any non-nullish value" -- in other words, any type that is not null or undefined.
By defining an intersection of string & {}, you effectively create a type that is "as wide as" the string type, but does not inherently widen -- therefore, when you create a union between | , the result is you can have or any string as a value.
I believe this trick was made redundant at one of the last versions, as they made “| string” not erase the specific strings the type consisted of
I tried that but it does not seem to work
I need exactly help on this one please.In a react I have a AriaRole type that also has this intersection type of string and object. Now I want to create a new type from the AriaRole without this intersection type. I have tried many ways but still couldn't able to omit or Exclude this intersection type. Do you have any solution for this ?
Remember Matt Pocock talking about this and saying it is really odd. Still personally think it is and do not use it commonly.
Like most of the time, it depends. I think it‘s great for certain features where you want to have some suggestions but not be restricted.
100% on the use case of it, but the "string & {}" is what we both find odd. Think this was back in TS v4.8 roughly.
Do you have a video where you talk about how to make ts flag errors for missing properties within a complex object?
For example: export interface normalForm {
rules?: {
basicValidation?: rules;
yupValidation?: Yup.Schema;
};
toInput?: DynamicTextFieldProps;
component?: ReactNode;
grid?: RegularBreakpoints;
} The toInput property, when I do not set the required properties, does not give me an error in ts, even when setting the toInput prop as required.
Currently not, but I will look into it 👍
TBH this snippet is lacking a lot of context that would explain why you don't get a TS error, but generally speaking, nested object properties don't have an impact on TS being able to accurately report validation errors unless you're doing something ludicrous or over-using the any type
If you had explanations of things like the Yup.Schema, DyanmicTextFieldProps, TextFieldPropsType, etc etc...
awesome video!, what if i want the autocomplete but the string must be required ? i mean not empty string?
Then I would suggest to use the never type additionally:
type NonEmptyString = T extends „“ ? Never : T.
And then wrap the Autocomplete generic
How does it work? Isn't it a TypeScropt bug?
No, that‘s how the type system works in typescript. It works as design 👍
@Typed-Rocks is it documented somewhere?
pls, make a video like this, but for objects that have certain keys, but isnt restricted for those keys. something like: type Type = { key1: type, key2: type, [key in string]: any }
Will look into it. Thank you for your suggestion
Generally, that would be Record & { key1: SomeType; key2: SomeOtherType }
So the autocomplete is available only becuase null and undefined arent allowed? I would like for you to post an explanation on why it works
I will do a video about it soon 👍
if u could figure out how to get the keys from a map that would be awesome 👌
Could you provide an example? Then I will look into it 🤘
If you're talking about the JS native Map object, then the only real way to "know" the keys/values are to build your own type over it that either takes in a shape type that you can extract the "keyof" from, or utilizes a builder-style type to provide known key names to a final object