Most React devs don’t understand generic components

Поделиться
HTML-код
  • Опубликовано: 28 июн 2024
  • 00:00 Intro
    00:29 The Problem
    02:01 Adding a Type Parameter
    03:05 Checking What's Inferred
    04:11 Adding a constraint to the type parameter
    05:20 Outro
    Become a TypeScript Wizard with my free beginners TypeScript Course:
    www.totaltypescript.com/tutor...
    Follow Matt on Twitter
    / mattpocockuk
    Join the Discord:
    mattpocock.com/discord
  • НаукаНаука

Комментарии • 107

  • @ColinRichardson
    @ColinRichardson 5 месяцев назад +121

    Such a Generic title

  • @helleye311
    @helleye311 5 месяцев назад +14

    Absolutely love generic components. It's so nice to get all the highlighting. I usually extend {id: string}, this way I can add a key for mapping based on the id, and in most cases I have id in all the objects I'm displaying anyway.
    Another cool use case is a custom dropdown that takes a list of objects. Then you can also have a custom typesafe onChange. Also works great in combination with discriminated union props, if you pass 'multiple' prop your onChange is (value:T[])=>void, if you don't it's value:(T | undefined)=>void. It does get a little messy with so much functionality packed into one component (I also had a dropdown that would make a fetch call for data to populate it, which of course also was returning T[] for type safety), but I'm sure there's a way to make it more concise with some custom hooks and separate components.

    • @QwDragon
      @QwDragon 5 месяцев назад

      Completely agree!

    • @SpaghettiRealm
      @SpaghettiRealm 5 месяцев назад

      You can also extends object wich in js everything is object 😂

  • @pedrohenriquepires9484
    @pedrohenriquepires9484 5 месяцев назад

    Wonderful content, as always! I wrote a small article on about this some time ago, inspired by your content about generics. Thank you for your amazing work!

  • @tomasburian6550
    @tomasburian6550 4 месяца назад

    Learned generics quite recently and love them. Great videos here, Matt. Subscribed for life.

  • @JohnBuildWebsites
    @JohnBuildWebsites 5 месяцев назад +1

    THANK YOU! Was having that exact error when spreading a field object for a form today. Will try adding Extends Record to the type first thing tomorrow!

  • @krauterfrischkase8939
    @krauterfrischkase8939 5 месяцев назад +20

    Wouldn't it be "more react" to render the list of rows outside the Table component and then insert that into the Table children? React's philosophy emphasizes composition over configuration which would make the Table component versatile and more flexible.

    • @proosee
      @proosee 5 месяцев назад +6

      I'm not an expert, but I don't think it is crime to have one component with two renderers instead of two components, especially if they never will be used separately, because how you want to use row without table?

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад +6

      No, both ways are equally "React"

    • @ra2enjoyer708
      @ra2enjoyer708 5 месяцев назад +1

      It depends on the usecase. Especially in cases of tables, which have fixed structure declared by headers and can be expressed as 2D arrays easily, consuming JS types instead of opaque React components allows to wrap the results in row components with consistent styling. Versatility of children has a price on its parent in the form of forcing it to support a union of all children options. I.e. something like linebreaks for too long values in a column are trivially to compute from a string, which has a known length, while font size and all paddings are known to the table component, since these values are controlled by it in the first place. Interacting with them as components however devolves into hacks around React API and DOM-related boilerplate for counting pixels (good luck dealing with off-by-one errors caused by rounding errors).

    • @QwDragon
      @QwDragon 5 месяцев назад +3

      It's a simplified example. If he would've shown you smth real it would've take several hours to explain.

    • @Netherlands031
      @Netherlands031 5 месяцев назад

      Yeah but then the table can't for example do pagination, where the table keeps track of the page, slices the list of data and then only rendere that data with the render function

  • @JulianColeman03
    @JulianColeman03 5 месяцев назад +2

    I think more impressive than the explanation of generics was how you produced that intro zoom out effect by adjusting the focal length. Amazing

  • @sludgepuppy
    @sludgepuppy 5 месяцев назад

    Thank you, really nice and clear and simple 👍

  • @henrym5034
    @henrym5034 5 месяцев назад

    Concise and to the point 👍🏼

  • @ChesterRivas
    @ChesterRivas 4 месяца назад

    You're the best dude. And I follow you on everything.

  • @JeyPeyy
    @JeyPeyy 5 месяцев назад +3

    Great video! The only change I'd make is to use unknown instead of any in TRow extends Record

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад

      In this case because we REALLY can pass anything into that Record, it doesn't matter which one you pick.

    • @foolmoron
      @foolmoron 5 месяцев назад

      @@mattpocockuk I think the better answer here is that `any` is perfectly good to use in a generic type constraints, since the `any` is never used by an actual instance of a variable, it's just a constraint. If you were typing an actual obj variable whole values could be anything, you should really use Record, so that TS can encourage you to do the appropriate type narrowing or casting on the values that you get from that obj.

    • @barneylaurance1865
      @barneylaurance1865 5 месяцев назад

      @@mattpocockuk I'd think if they both work equally well then unknown is preferable. I avoid any as much as I can, I think of it as just a last resort escape-hatch for when you don't want or can't deal with static typing. It's weird blend of type type and bottom type at the same time.

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад

      @@barneylaurance1865OK - but that rule of thumb doesn't make sense in a context where you want the widest type possible. In that case, either are fine.

  • @pavloomelianchuk
    @pavloomelianchuk 5 месяцев назад +5

    Looks like another try for me, to understand "generics" in react.
    Eventually it will come 😜
    Thanks!

  • @QwDragon
    @QwDragon 5 месяцев назад +2

    A very big problem with final code: using lambda fuction as a component will force recriating of everything it renders every time and will make completely impossible to keep any state anywhere inside.
    One more issue: no keys are specified on rows.

  • @billyfigueroa1617
    @billyfigueroa1617 4 месяца назад

    Matt your videos are amazing. I have learned a lot from you
    I do have a question. Is it possible to use different types for a generic component? for example, I have a carousel I want to create and I want it to be able to display different things. Like it can display an image and become a slider in a sense or it can just be a scrollable like hero section. Is it possible to have something like Carousel and Coraosel where Image and Info are objects that do not have the same attributes. Something like Image has name, urlImg and Info has header, subheader, content

  • @Shyam_Mahanta
    @Shyam_Mahanta 5 месяцев назад +1

    Where to buy typescript book you have behind?

  • @andrewpleshko365
    @andrewpleshko365 5 месяцев назад

    Great! Thank you

  • @joshmarom
    @joshmarom 5 месяцев назад

    That was great thanks

  • @mycodingtutorials
    @mycodingtutorials 5 месяцев назад +1

    What font is this? What theme and what IDE?

  • @user-nl3uu9cx3x
    @user-nl3uu9cx3x 5 месяцев назад

    Nice! Got it now

  • @dmytroprokoptsov9587
    @dmytroprokoptsov9587 5 месяцев назад

    Hi, Matt) Your videos as beautiful as your accent! But I'm here to ask you and your community a question: is there a way in TypeScript I can check on type level whether object has keys or not? I know it is possible with Arrays, but with objects seems to be not

  • @ninjarogue
    @ninjarogue 5 месяцев назад

    Excellent!

  • @CodeZakk
    @CodeZakk 5 месяцев назад

    Yeah to the point and concise tutorial.my question is can we object and array on the extends and also is Record a typescript feature?🎉🎉

  • @khoihoang8888
    @khoihoang8888 5 месяцев назад

    love it!

  • @Rebel101
    @Rebel101 4 месяца назад

    Hey Matt, question:
    See when you hover over react component jsx call, it just references type names instead of actual types like it does when you call is a a function.
    Is it possible to write a genetic type for ReactNode that solves this issue?

  • @hakuna_matata_hakuna
    @hakuna_matata_hakuna 5 месяцев назад

    can ytou do a video on extends , it seems like a super power

  • @engine_man
    @engine_man 5 месяцев назад

    Dropping gems as usual. I never understood with that Intrinsic attributes thing meant.

  • @Clem.E
    @Clem.E 4 месяца назад

    React Table nightmare now explained, thanks!

  • @MerthanMerter
    @MerthanMerter 5 месяцев назад

    amazing

  • @leftis95
    @leftis95 5 месяцев назад +1

    How come the comma "," solved the .tsx errors inside the generic type?

    • @Pete133
      @Pete133 5 месяцев назад

      Yes I would like to know why the comma makes a difference as well. Is it just a random buggy thing about typescript or are we missing something fundamental?
      *Edit* Ok so Matt says @2:40 that adding the comma lets typescript know that is a type parameter, but I didn't understand what else it would be other than a type parameter. Because we're using a const variables, which in a react app can be directly assigned to JSX... like const foo = ... typescript doesn't know that we're not trying to assign JSX. So adding a comma there lets typescript know we're using a type parameter rather than JSX.

  • @thepetesmith
    @thepetesmith 5 месяцев назад

    I’m confused a bit because I always use React.FC and pass a props Type as the type arg. I don’t get how in your example you aren’t passing the type, but the generic infers the type by argument order? I’ve seen how you can pass a type argument in the jsx with angle brackets

  • @versaleyoutubevanced8647
    @versaleyoutubevanced8647 5 месяцев назад

    do you have any thoughts on the fact we can't type children? ReactNode is more like Record for JSX
    I'm talking about complex children demanding like 3 childs, , and .
    Or restrict children to have only one child with data atribute 'foo'...

  • @DmitriiBaranov-ib3kf
    @DmitriiBaranov-ib3kf 5 месяцев назад

    I prefer going with Record. And Vue 😅

  • @iamandrewluca
    @iamandrewluca 5 месяцев назад

    Another subjective opinion is using instead of props.renderRow(row)
    The former version allows to call hooks inside of it, but it looks strange.
    The benefit of the former is that you can pass a component renderRow={MyRowComponent} but you lose type inference.
    I suppose the latter is the right way of rendering the row in this case props.renderRow(row)
    Btw, great video as usual, short and on point!

  • @ctpaintball4life
    @ctpaintball4life 5 месяцев назад

    Slotted components are really hot right now. Is it possible to do this type of inference with JSX?

  • @dennisgonzales9521
    @dennisgonzales9521 4 месяца назад

    2:38 addding a comma here throws me off, that why I prefer to use the classic function declaration for my components instead of arrow functions.

  • @leotravel85
    @leotravel85 5 месяцев назад +1

    Record whould have been better

    • @QwDragon
      @QwDragon 5 месяцев назад

      No. Always use any instead of unknown in generic constraints.

    • @leotravel85
      @leotravel85 5 месяцев назад

      @@QwDragonWhy?

    • @fow7139
      @fow7139 5 месяцев назад

      @@QwDragon No, you should not. Any is not restricted and can be considered as a bad practice. In critical environments, it actually is. In those cases, you want to make another component from Table that will correctly wraps the type or pass it as a generic :

  • @TeaBroski
    @TeaBroski 5 месяцев назад

    Wow, the knowledge this man ...spreads is always mind blowing

  • @syedhaider0916
    @syedhaider0916 3 месяца назад

    Probably the best generic video ever.

  • @saidabedi2836
    @saidabedi2836 5 месяцев назад

    🔥

  • @gerhardsteenkamp562
    @gerhardsteenkamp562 5 месяцев назад

    The confusing bit for me was that it doesn’t work the way generics usually work, in that you don’t pass a type argument to the component, but instead the generic acts as a placeholder to infer the type of regular props that are passed to the component. And then you can use that inferred type in multiple places in the component.

  • @churraskindequeijo8418
    @churraskindequeijo8418 5 месяцев назад

    Nice

  • @yah3136
    @yah3136 5 месяцев назад

    I will just ignore this video and ask youtube to not show it again just for this nice presumptuous title ... well done Mr "better than most devs" ...

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад +1

      It's just a shorthand for 'advanced content'.

  • @MaPhongBa129
    @MaPhongBa129 5 месяцев назад

    Put a comma after type param is magic, most of the time, I change function from arrow to normal function.

  • @akosbalint3485
    @akosbalint3485 5 месяцев назад

    Do somebody know how to add forwardRef to generic components?

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад +1

      github.com/total-typescript/react-typescript-tutorial/blob/main/src/08-advanced-patterns/65-forward-ref-with-generics.explainer.1.tsx

    • @akosbalint3485
      @akosbalint3485 5 месяцев назад

      @@mattpocockuk Thank you Matt!

  • @1felixxex1
    @1felixxex1 5 месяцев назад

    nice

  • @glorrin
    @glorrin 5 месяцев назад

    I have this problem in typescript I encounter from time to time, and no clue how to type it.
    function assignValue(myObject: TObject, key: TKey, value: Tvalue){...}
    TObject is usualy easy enough to type
    Tkey, is just keyof TObject
    But I have no clue how to get Tvalue to be typeof myObject[key]

    • @laminecherif7724
      @laminecherif7724 5 месяцев назад

      function assignValue(myObject:TObject, key:TKey, value :TObject[TKey]) {....}

  • @boris_raduloff
    @boris_raduloff 5 месяцев назад

    This is quite similar to lifetime annotations in rust but about types instead of memory freeing.

  • @andriiv7033
    @andriiv7033 5 месяцев назад

    Great. The only thing is that the any should be replaced with a generic type too, shouldn’t it?

  • @KoenVerheyen
    @KoenVerheyen 5 месяцев назад

    Don't need to add a key-attribute in this case?

    • @DemanaJaire
      @DemanaJaire 5 месяцев назад

      Still needed

    • @QwDragon
      @QwDragon 5 месяцев назад

      Yes, but that does nothing while he uses lambda function as a component...

  • @orcdev
    @orcdev 5 месяцев назад

    But what about my eslint rule "no any" 😅

  • @buggycoder7900
    @buggycoder7900 5 месяцев назад

    can't we have ??

  • @Wielorybkek
    @Wielorybkek 5 месяцев назад

    can't it be a footgun? whenever I see hacks like this I think that such use case was not considered by framework authors and they might not support it. it might stop working at some point or have bugs later along the way.

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад +2

      No, it's not a footgun. It's an extremely common pattern used in tons of different libraries.

  • @Hugos68
    @Hugos68 5 месяцев назад

    Congrats on becoming a father!

  • @macon5696
    @macon5696 5 месяцев назад +1

    some time ago, i've managed the same problem, but i did't find solution to do the same with memo without TS issues

  • @iMakeYoutubeConfused
    @iMakeYoutubeConfused 5 месяцев назад

    The only time I'm not guilty of

  • @Krzysiekoy
    @Krzysiekoy 5 месяцев назад +3

    How does typescript "decide" which prop to use for the type inference for the type argument T?
    Take this example:
    const Table = ({rows, randomProp}: {rows: T[], randomProp: T}) => {
    return rows
    }
    Table({rows: [1,2,3], randomProp: "foobar"})
    This will error out saying "Type 'string' is not assignable to type 'number'." Okay, why does rows prop take precedence? Why doesn't this error the other way around, i.e. "Type 'number []' is not assignable to type string [ ]".
    What makes the "rows" component "win" when it comes to type inference?

    • @abdallahm96
      @abdallahm96 5 месяцев назад

      Have you tried switching rows and randomProp when calling the function?
      I think that's where it's inferencing. Another way would be specifying it (e.g. Table(/*args*/) )

    • @Krzysiekoy
      @Krzysiekoy 5 месяцев назад

      @@abdallahm96 Yes, I've tried switching the props around and it makes no difference.

    • @vytah
      @vytah 5 месяцев назад

      It doesn't matter except for the way the error message is formatted.

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад +3

      Fabulous question. In your example, there is no true way to figure out which one goes first. So TypeScript picks one - I'm sure the compiler knows which, but I don't have an intuition for it.
      To customize which one gets selected, you can use NoInfer from ts-toolbelt. NoInfer will be coming to TypeScript in 5.4.

    • @Krzysiekoy
      @Krzysiekoy 5 месяцев назад

      @@mattpocockuk Thanks Matt! Appreciate your answer.

  • @lifeiscontent
    @lifeiscontent 5 месяцев назад

    Fix forwardRef so these kinds of components aren’t a nightmare to build

  • @GilEpshtain
    @GilEpshtain 5 месяцев назад

    the only downside is that you have to use the syntax "const C = (..."
    and cant write "const C: React.FC = ..."

  • @michamazurkiewicz80
    @michamazurkiewicz80 2 месяца назад

    Speaking from experience this is really bad to show to people. XD I have an app with 10 or more tables and initially someone had idea why not just making single table for all of them...
    But it doesnt really make sense cause each of these tables is different... has different actions, items, filters...etc... that we ended up with a monster that tides our entire application. Whenever you make change you have to go over entire app to check if everything is working etc.
    Lesson learned.. just copy the code and have 2 tables or 10 tables that are all simple and clean. Have logic only interesting for them and you dont have to worry to break entire app when making change in single table. So in this situation big no to generics.

  • @fabius-maximus
    @fabius-maximus 5 месяцев назад +1

    Ugh vue does this way cleaner

  • @kirylbehansky1315
    @kirylbehansky1315 5 месяцев назад

    Only react devs can’t understand that 😢

  • @paxdriver
    @paxdriver 5 месяцев назад

    I still don't understand the benefit of typescript. This is way more mental load than writing a proper function with guardrails. How is this easier than vanilla js?

    • @Netherlands031
      @Netherlands031 5 месяцев назад +2

      Because typescript will now error in the renderrow function if you try to access a row property that doesn't exist. And you get autocomplete if you do 'row.' inside of renderrow. So a much improved DX for the cost of just a few lines of code. The mental load is pretty small if you have some practice with this stuff

    • @deniyii
      @deniyii 5 месяцев назад +2

      It’s not supposed to be easier. It’s always going to be easier to write untyped spaghetti than any typed language.

    • @awekeningbro1207
      @awekeningbro1207 5 месяцев назад

      js is waking on sahara desert, ts is walking on a well built road on sahara

    • @paxdriver
      @paxdriver 4 месяца назад +1

      @@Netherlands031 maybe i'll figure it out one day, just started with it this year and i've been writing js for over 20 yrs so it's probably a stubborn bias of mine, i'll admit. i'm trying to see it, though

    • @paxdriver
      @paxdriver 4 месяца назад +1

      @@deniyii you can type javascript yourself if you write functions for it. that's my whole point. instead of focusing on all this new syntax and stuff it's cleaner code and easier to write when you yourself wrote the typechecking function. const kind of solved this for anyone who puts in a few minutes to write little type checking functions imho.

  • @bhalsodbhavin9557
    @bhalsodbhavin9557 5 месяцев назад

    First view

    • @ColinRichardson
      @ColinRichardson 5 месяцев назад +1

      You managed to be the first view? Even though I commented a full minute before you?
      I am not sure that logic checks out.

    • @DemanaJaire
      @DemanaJaire 5 месяцев назад +3

      The fact that both of you even care.

    • @ColinRichardson
      @ColinRichardson 5 месяцев назад

      @@DemanaJaire I mean, I "could leave things being wrong"..
      But, then, we would still be going to libraries to get information instead of having the internet..
      Progress and removing incorrect things must be done

    • @Keisuki
      @Keisuki 5 месяцев назад

      First reply

  • @badwolf9090
    @badwolf9090 3 месяца назад

    They're not parentheses, we call them brackets in British English, don't let the Americans take over :-(

  • @echobucket
    @echobucket 4 месяца назад

    If you would just stop making stupid const arrow functions and use the dang function keyword it wouldn't get confused about the type argument and you wouldn't have to use the comma.

  • @macieja92
    @macieja92 5 месяцев назад

    Fuck me, hah I smiled when I realized that it's so freaking easy to understand while I've been having so many problems with understanding generics 😂

  • @AnthonyPaulT
    @AnthonyPaulT 5 месяцев назад

    can you do `extends object` instead?

    • @mattpocockuk
      @mattpocockuk  5 месяцев назад

      That's a bit looser, since arrays are also objects.