How Did I Not Know This TypeScript Trick Earlier??!
Vložit
- čas přidán 4. 07. 2023
- This TypeScript trick to conditionally include types is so useful, especially for React props. I've asked myself how this is done many times before, and it's just really cool to learn something so simple yet useful.
-- links
Discord: / discord
My GitHub: github.com/joschan21 - Věda a technologie
Great video! Here's the correct terminology to help people google this stuff:
Props is a discriminated union.
'gender' is the discriminator.
The if statements you use to check members of the union aren't type guards, that's something slightly different. You're doing 'narrowing'.
Love seeing these tips out in the wild!
I thought this was a parody account at first
Thank you for the terminology, I feel missing that makes it impossible to learn and compare techniques, so it is very much appreciated.
so what is type guards then?
@@gubatenkov Type guard is when you test a variable on which type it is in an if statement to narrow it down. You can use e. g. instanceof, typeof or the in keyword for this. There is a great logrocket post about this, if you want to know more! just Google type guard typescript and you will find it
@@gubatenkov Type guards are:
const isString = (val: unknown): val is string => {
return typeof val === 'string';
}
You use the 'is' syntax to annotate a function to be a 'type guard', or 'type predicate'.
This is a trick I have been using for a while and really love it. One thing to note about this is that normal TS utility types like Omit and Pick will not work with Unions so you need to write your own version of these utils that look like this.
export type UnionOmit = T extends unknown
? Omit
: never
Thanks
Interesting. Thanks for sharing man!
Small tip, the type string | number | symbol is equal to the global type PropertyKey :)
type Foo = PropertyKey;
// ^? string | number | symbol
Why doesn't it create a table in my local db it pushes and migrate but does not create a table why tried many solutions not working
Great trick, I used to get the same result with function overloading but that require a lot of type checking.
For those curious, these are called tagged unions or discriminated unions. The typescript docs mentions them. They are common in functional languages and also in most newer languages like Rust. They are from a category of types called "sum types". What we're used to in OOP languages are usually "product types"
well said 👍
I learned this from elm
I think that it's better to write code more explicit
interface Person {
name: string;
}
interface Male extends Person {
gender: "male",
salary: number;
}
interface Female extends Person {
gender: "female";
weight: number;
}
type Props = Male | Female;
oh, that looks so much better
composition vs inhertitance?
@@lasindunuwanga5292 there is no inheritance because it is an interface. interfaces cant inherit as they dont have behavior
@@bernardcrnkovic3769 so you are telling me it is okay to build an heirachy of interfaces?
There is no need for using interfaces.
Interfaces are much more extensive in terms of functionality than regular type aliases.
Using the extends keyword is not more "explicit". Intersection types (with the & operator) have pretty much the same semantics.
Interfaces should be used when describing abstract types (for example, when doing polymorphism).
You should not use an interface if you're not planning on using the implements keyword on other concrete types.
Interfaces are not as flexible as type aliases, because they only allow object types.
Also, interfaces can be overwritten in other places of the code, just by having another definition of it, or even multiple other definitions.
Type aliases are much more simple and generic, they are just a way to give some type a name in order to re-use it or export it.
I would prefer something like that:
type Person = {
name: string
}
type Male = Person & {
gender: 'male'
salary: number
}
type Female = Person & {
gender: 'male'
weight: number
}
type Props = Male | Female
I believe these are called discriminated unions
Yes, those are discriminated unions
I'm surprised so many people are finding out about this so late
Males are required to declare their salary, while females are required to declare their weight? Indeed, a very discriminatory union 😂
@@markzuckerbread1865 i feel like disciminated unions are 90% of what makes typescript awesome, everyone should know it
It's so weird that the newer typescript docs removed the docs for discriminated unions. You can only find it in the old docs
I've been looking for this specific trick for a very long time to no avail! thank you man! 🔥
Same, really glad it's this easy
Please do more videos like this. This is very beautiful and and very nice. This can makes me refactor whole bunch of my component props type kudos Josh ! Great content !
Something similar happens when using the Zod parser. If you look the generic output type, you’ll notice how the isSuccessful being true is constraint to the output that contains the data while isSuccussful being false is constraint to the output that produces and error. The parsing logic os handled within the library but can be a good source of inspiration for these types of unions.
Great video 🎉
Mhmm that's interesting too. Thanks for sharing man, cheers
Saw this video when it first came out and thought it was really cool but never had a need for it until today so now I'm back to refresh my memory. Thanks Josh for this awesome little trick
I just found your channel and I am really liking it. Good quality stuff :D
This was awesome man! Great explanation, been wondering how to get those conditional types to be so easy and you just nailed it!
I really enjoyed learning that awesome trick you shared, especially with the helpful examples you provided. Your thoughts were also well-organized, which made it easy to follow along. Thank you!
Cheers man
I was trying to figure out how to do this recently with no luck, so glad you uploaded this mate!
I spent the whole day today trying to figure out how to do this. Thanks to you and CZcams recommendations for this info!
Great vid Josh 👍🏻 Always amazing when you learn these sorts of tricks in TS. One I learnt yesterday that's pretty cool is if you want intellisense for a string type that has set responses as well as the ability to pass any string you can do: type Gender = "male" | "female" | "prefer not to say" | (string & {}); now when you bring up autocomplete rather than getting nothing you'll get male, female, and prefer not to say listed in the dropdown. (Credit to Matt Pocock for this one)
Can you kindly provide the link to this video?
@@Osirisdigitalagency Sure! czcams.com/video/8HoOxOd86M4/video.html
I heard about that too! Very cool concept
Love the way you explained, Loud & Sharp.
Subscribed
Life saving!!! I was looking for this for a long time. Thanks for the tip man! :)
Loved the examples, thank you Josh!
THIS IS HONESTLY SO COOL. I was actually in a similar position, where I wanted conditional typesafety, but I legit couldn't find exactly what I was looking for. And this video was just that. I honestly loved it, THANK YOU SO MUCH!!
You're welcome dude yeah I was really happy when I discovered this too
im glad your getting into typescript
Keep up the great work Josh!
I randomly found out your channel and I'm happy. Quality content as always
Thank you mean. That means a lot.
amazing Josh, thanks for sharing and to the point, also noticing the real use cases!
This is exactly what I've been looking for. I've kind of just dealt with the possibly undefined properties. Great explaination!
DAAAAAAAMN!!! I have searched for this ages!!! I tried to make it with conditionals but this way is just what I needed :D thanks for share!
awesome dude
Really needed this since a long time!!
Great video. I needed this quite recently. Thanks.
This is awesome! I ran into rhis problem recently and ended up just allowing the user to pass all the props without discrimination but knowing this now is a huge help!
The only downside i see to this is that you will keep getting typerrors until you fulfill all the requirements by passing all the necessary props but that's a small trade off for something so powerful
You could still make them optional! There really are some great benefits to this
Thank you so much for this video, you just shipped it when I was looking how to do exactly it! 😂😂
I think this is super useful, and I too with I would have known this sooner. Thanks for posting!
bro I have been searching for a way to make conditional types, thank you!
Just discovered this about the same time as you, it seems to be. Good video!
When I use Typescript and React, I like to create an interface and extend the HTML attributes onto it like so:
interface MyComponent extends React.HTMLAttributes {
// Extends props
}
And that will add className, name, onClick, and all other attributes related to an HTML DIV Element.
Do you spread the rest of the props? Or do you destructure each prop of the native element?
Thanks man!! Have been looking for a solution for this scenario.
In the case of multiple branches, you could use “satisfies” to ensure that the keys and types do not get mixed up. For instance “satisfies { gender: string }”
hey that sounds interesting, could you explain it a little bit more? :) that sounds interesting but I can't really figure out what exactly to do with the "satisfies" keyword here
@@ExtraterrestrialCatgirl yeah, maybe a code snippet would do
the satisfies keyword is super interesting too. Gonna look more into this typescript stuff, it's actually cooler than I thought
@Joshtriedcoding1 love the realization 5 hours later 😂😂
Awesome video. Very clear and helpful. Thank you!
Cheers man
Bro you just saved me right now, I was searching for this 👍👍👍
Nice trick! Thanks for sharing.
Great video! By the way, in the conditional statement, because there are only 2 options for the gender property, you can turn the else if into just an else. Conditional types are one of the most powerful features in typescript. I love your videos, keep going 🙂
What do you mean? Do you have a small example of it?
Nice video, thank you. You could add exhaustive switch on union types when using it:
export function matchGuard(_: never): never {
throw new Error('Unreachable code in match guard')
}
In code using your person
if(props.gender==='male'){
return ''
}
if(props.gender==='female'){
return ''
}
matchGuard(props)
If you add another gender later the line "matchGuard(props)" will give you a compile error.
that's something that i needed really much
thanks
Great job josh
This is an extremely useful feature in TypeScript. Often the usage of a union type (separated by a pipe "|") that can be distinguished by a literal value is known as a "discriminated union". Researching that is extremely helpful for finding use cases.
Very useful, thank you!
ahhhh i recently did something similar but used if statements. this is so much cleaner and makes more sense. def a good tip
Bro huge thanks to you , i comment really rare, but the information you shared in this video helped me so much
HELL YES!! Nice video!!
Thanks! Great video
Great explanation. Thank you.
I literally just recommended this method at work today. Since I use only types and not interfaces, I had to come up with creative ways to do conditional types.
Very well done Sir! Thank you
sooo cool and intuitive!
Great video!
TypeScript is an incredibly powerful language for describing your APIs with very specific types. It's one of my favorite languages because of this.
This was clearly explained and feels useful 🎉
Thank you sir! Gonna be able to use way less question marks now
Wow. I was trying to google this for weeks and didn't quite got the correct terminology. Thx for sharing!
very neat trick! please show more videos!
This is very helpful. Thanks
@joshtriedcoding It's possible to destructure it in Props! You can use weight?: never and salary?: never for the other Type. So f.ex. on MaleProps it will be { gender: string, salary: number, and weight?: never }. This way all properties will exist on all PropTypes and destructuring will be possible. Both salary and weight will be typed as number | undefined and you can easily typeguard them if needed.
Oh interesting. Weird that they don't show up in the intellisense
@@joshtriedcoding they do once you add `weight?: never` / `salary?: never` as I described :) Only then they will "always exist" and intellisense will pick them up
0:16 I like how you're using components from other file without even importing them. That's more magical to me
Unless he's using a plugin that manages imports behind the scene, I'd say this is because of a bug in VS Code that suppresses import errors when the target isn't exported. TypeScript won't compile it in this case, though.
yeah NO idea why this is, my vscode just started doing it. based
Because both child and parent are not modules, so Typescript treat them both as globals
Awesome trick and well explained! Could you maybe elaborate on the VSCode keyboard shortcuts you are using to destructure types, to see necessary types and the like?
Very useful video. thanks 👌
Just 4 days ago, I figured out something very similar for my OSS project. I figured out how to make an xor type that is like this, but will require props from the 2 different set of types to be mutually exclusive. Just 1 set of props or the other, not all.
Care to share?
Something like
{
a: number;
b?: never;
} | {
a?: never;
b: string;
}
?
@@Ultrajuiced I've tried responding but my youtube comments are always deleted. oh well
Great content! could you make a TypeScript series about all those advanced concepts? Would be great. Or maybe a course will be appreciated.
TypeScript is going to be yummy han anytime. Appreciate sharing the valuable tip.
Need more such videos
I finally had a use case today where I applied this approach. 🙂
Thanks for sharing. Useful tool in reducing errors and leveraging intelisense.
You can setup ApiResponse or ApiResponse for Error response and allow to use it as ErrorApiResponse in any Api not only for number
Nice explanation man 👍.
Let me go refactor my code, this is exactly what I need earlier. Thank you Josh
You're welcome champ
Ohh that's cool😮. Thanks for the trick
youre welcome dude
Typescript has some truly awesome tools for building types. I cannot recommend reading the full docs enough.
Awesome, thank you!
Wow, you got an insta sub from me, man, super video!
this is pretty good actually, thanks!
this is in the documentation?¡ :o
I just recently learnt about the TS "in" keyword. It's also really useful for union types where you don't have a discriminator like the "gender" field in this example. You should do a vid on that too!
Gonna look into it, thanks for the suggestion dude
That's not exclusive to TypeScript. JavaScript has it, too
@@DubiousNachos huh yeah TIL. It does really help specifically in TS cos of what I said above. That is you can't even do something like 'if (person.salary === undefined)...' in the above case but you can use the 'in' keyword
good tip!, this tips are super usefull!!
The video uses React which makes the example a bit awkward because I’d recommend against coupling your display components to conceptual types - try to keep React components generic, dealing with the display of the data, not complex types. Why? Otherwise your display becomes tightly coupled to your complex types, and you end up needing to pass in a whole ComplexPerson to get a SmallWidget component to work. You may have felt this when you needed to add a new property to a mock in a component that doesn’t even use the property you just added. This actually also applies to non-React functions too. A useful question is “is it really a Person that I need to pass in here? Or am I dealing with something that just kinda looks/feels like a Person?” Coupling is one of the biggest pains to deal with when writing software and ideas like this can help you avoid it.
this person knows what they’re talking about. this is a great point for reusability and testability.
@@patrickeriksson1887 if you know, you know. I’ve been coding for 20 years now 🙈
Junge, heute noch das Problem gehabt und zufällig haust du nen Video raus. Danke dir 👍
You're welcome dude, nice to hear
nice video man
Nice! Thank you very much
Muchísimas gracias, excelente!!
Awesome video
Cool video, I was wondering what shortcut/ extension was used when clicking on the type to have it added to the deconstructed props.
so I can use this for passing same props with different types ? example passing files that can be files type and custom file type . Currently using 'as' when passing props and using props.
This is wonderful
i don't even program with typescript but i'm so interested in these kind of things
Great 😃👍 I learn something new in typescript.
nice!
But what if you use prop destructuring to check if all needed props ar given? I get a does not exist on that, because not in every case its present. What we do here then? Conditions in the destructure are not allowed. So this approach only work when using unspecified, nonchecked props?
I've previously achieved this with Never type, but not seen this approach before. Interesting
lovely one
Thank you for this
Neat! I might suggest starting with something super explicit like this introducing additional complexity to the type system as needed.
type Props = {
person: Person
}
type Person = Male | Female
type Male = {
kind: "Male"
name: string
salary: number
}
type Female = {
kind: "Female"
name: string
weight: number
}
Awesome awesome awesome.
Thx ! 😊
Similar behavior is when u use union type of two interfaces, then check with keyword "in" is property in object
Really cool trick!
Downsides I see compared to the "classical" way with Interface Male extends Person {...} is:
- it is harder to read for other team members (React novices or even non React developers)
- it works only binary. As soon as you have more than two options, you need to refactor the code anyway
(and no, I didn't meant to start any gender discussion rn)
1:22 how did you do this, click on property name in the interface and got it desctructured in props in the component?
This guys points out some really obvious things that I have been using for a while but looking at comments apart from 2 or 3 comments everyone finds these tips useful. It leaves me baffled.