Most React devs don’t understand generic components

Поділитися
Вставка
  • Опубліковано 10 лип 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
  • Наука та технологія

КОМЕНТАРІ • 108

  • @ColinRichardson
    @ColinRichardson 5 місяців тому +121

    Such a Generic title

  • @tomasburian6550
    @tomasburian6550 5 місяців тому

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

  • @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 😂

  • @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

  • @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!

  • @sludgepuppy
    @sludgepuppy 5 місяців тому

    Thank you, really nice and clear and simple 👍

  • @henrym5034
    @henrym5034 5 місяців тому

    Concise and to the point 👍🏼

  • @PickleRiiiiiiick
    @PickleRiiiiiiick 5 місяців тому

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

  • @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

  • @pavloomelianchuk
    @pavloomelianchuk 5 місяців тому +5

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

  • @Shyam_Mahanta
    @Shyam_Mahanta 5 місяців тому +1

    Where to buy typescript book you have behind?

  • @user-nl3uu9cx3x
    @user-nl3uu9cx3x 5 місяців тому

    Nice! Got it now

  • @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!

  • @mycodingtutorials
    @mycodingtutorials 5 місяців тому +1

    What font is this? What theme and what IDE?

  • @joshmarom
    @joshmarom 5 місяців тому

    That was great thanks

  • @andrewpleshko365
    @andrewpleshko365 5 місяців тому

    Great! Thank you

  • @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.

  • @billyfigueroa1617
    @billyfigueroa1617 5 місяців тому

    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

  • @ninjarogue
    @ninjarogue 5 місяців тому

    Excellent!

  • @khoihoang8888
    @khoihoang8888 5 місяців тому

    love it!

  • @Rebel101
    @Rebel101 5 місяців тому

    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?

  • @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

  • @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.

  • @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?🎉🎉

  • @syedhaider0916
    @syedhaider0916 4 місяці тому

    Probably the best generic video ever.

  • @ctpaintball4life
    @ctpaintball4life 5 місяців тому

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

  • @engine_man
    @engine_man 5 місяців тому

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

  • @Clem.E
    @Clem.E 5 місяців тому

    React Table nightmare now explained, thanks!

  • @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

  • @hakuna_matata_hakuna
    @hakuna_matata_hakuna 5 місяців тому

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

  • @TeaBroski
    @TeaBroski 5 місяців тому

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

  • @LetalisLatrodectus
    @LetalisLatrodectus 7 днів тому

    Why use any in the Record instead of unknown?

  • @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'...

  • @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!

  • @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.

  • @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'.

  • @saidabedi2836
    @saidabedi2836 5 місяців тому

    🔥

  • @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 :

  • @dennisgonzales9521
    @dennisgonzales9521 5 місяців тому

    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.

  • @andriiv7033
    @andriiv7033 5 місяців тому

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

  • @DmitriiBaranov-ib3kf
    @DmitriiBaranov-ib3kf 5 місяців тому

    I prefer going with Record. And Vue 😅

  • @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

  • @orcdev
    @orcdev 5 місяців тому

    But what about my eslint rule "no any" 😅

  • @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.

  • @MaPhongBa129
    @MaPhongBa129 5 місяців тому

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

  • @1felixxex1
    @1felixxex1 5 місяців тому

    nice

  • @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...

  • @buggycoder7900
    @buggycoder7900 5 місяців тому

    can't we have ??

  • @Hugos68
    @Hugos68 5 місяців тому

    Congrats on becoming a father!

  • @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!

  • @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

  • @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.

  • @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.

  • @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.

  • @fabius-maximus
    @fabius-maximus 5 місяців тому +1

    Ugh vue does this way cleaner

  • @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 4 місяці тому

    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.