Rethink your life choices before you do this...

Поделиться
HTML-код
  • Опубликовано: 15 окт 2023
  • 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
  • РазвлеченияРазвлечения

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

  • @ivanbragin7932
    @ivanbragin7932 8 месяцев назад +68

    Yes, exactly! Every time when I was working with poly components the code was so much easier to work with after I rewrote them to multiple components. I feel like with all this flexibility people tend to forget the basics of component composition and why it is a thing in a first place.

    • @soviut303
      @soviut303 8 месяцев назад +1

      While I'd normally agree, a button and "link that looks like a button" make for a very good combination that I'd still consider atomic, especially when using components to encapsulate styles like you would with Tailwind. You can do this in Vue components without any type issues or accessibility issues because it all lives in the template instead of the code.

    • @raenastra
      @raenastra 8 месяцев назад

      ​@@soviut303I agree, and that's one of Tailwind's tradeoffs: you're encouraged to group style directly with markup, making reusing the style across two similar components with slightly different markup (button and anchor) awkward. A CSS class is helpful here - grouping styles which can be reused where needed.

    • @soviut303
      @soviut303 8 месяцев назад

      @@raenastra The css class is the temptation, but that always leads to further abstraction on top of a component which is already an abstraction. There's a reason "@apply" is strongly discouraged in tailwind. If I really had to split the component I'd have a base they both nest. Again, this is only a React issue; in Vue it's easy to make an "Action" that can behave as a button or a link based on props that is type safe and simple.

    • @raenastra
      @raenastra 8 месяцев назад

      @@soviut303 I know, but I do think making a button class for the button/anchor is the specific situation where it's nicer to make an exception to Tailwind's recommendation on this.
      Having used Tailwind quite a bit, the tradeoff of using components for styles is that you group the styles AND the markup together. This is normally what you want, but for button/anchors it breaks down because you want a style which isn't directly tied to markup.
      CSS classes are exactly this, just make a button class.

    • @soviut303
      @soviut303 8 месяцев назад

      @@raenastra Vue has a primitive so I don't have to do any complex template juggling and all my classes can live on it in one place. If it has a "to" prop it's a router link, if it's an href it's a regular link, if it has neither it's a button. All that react boilerplate disappears.

  • @glorrin
    @glorrin 8 месяцев назад +52

    "If you want to create a [...] polymorphic component, first of all rethink your life choices..."
    My collegue told me more or less the same thing when I created a realy cool polymorphic component.
    Utterly useless and could be easily simplified but trust me it was subjectively cool !

    • @AmodeusR
      @AmodeusR 8 месяцев назад

      It always feel fantastic trying to squeeze the most functions out of a single component, even though separating it would make things much simpler xD

    • @davidkimmich6900
      @davidkimmich6900 8 месяцев назад +1

      yeah exactly, like you said. The component then often lacks of readability or is too cryptic. Similar to having multiple variants. Been through it, can't recommend xD.@@AmodeusR

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

      ​@@davidkimmich6900 I managed to make the ultimate abstracted abstraction it can run anything:
      ```ts
      function runIt(run: (...params: TT[]) => TU, ...it: TT[]){
      return run(...it);
      }
      ```

    • @catalinpreda4666
      @catalinpreda4666 8 месяцев назад

      I thought it's just me failing with YAGNI & KISS as a junior 😂

  • @eero8879
    @eero8879 8 месяцев назад +3

    This is so convoluted, just make 2 component and let the calling function choose. This will also simplify types and make easier to properly type props.

  • @juliohintze595
    @juliohintze595 8 месяцев назад +13

    I struggled with this in the past. It was the exact same situation: a button and a link had a lot in common, like styles and some behaviours. I managed to make one component for both, but seriously? It's MUCH MORE simpler to just make two separate components. They can still share the styles and some behaviours. I don't care if I have to duplicate a little bit of code. The typing becomes much simpler and manageable that it makes up for it.

    • @joelv4495
      @joelv4495 8 месяцев назад +1

      Yup. Keep them separate. If you really want to keep the code DRY, then just move the common elements/behaviors to a helper file that each component then imports from.

    • @andrewmcmaster7123
      @andrewmcmaster7123 8 месяцев назад +2

      I was just doing this today updating an older code base, saw this a lot and it didn’t save much but it made it a pain to deal with. Ended up just making two components

    • @joelv4495
      @joelv4495 8 месяцев назад

      @@andrewmcmaster7123 yup. After singlehandedly turning my codebase into an absolute disaster, I think a lot more about what future me will see when they read the code. Single responsibility code is much easier to reason about without needing a whole lot of context.

  • @ccccjjjjeeee
    @ccccjjjjeeee 8 месяцев назад

    Fully agree, but much more importantly this is one of the best title + thumbnail combos I've ever seen

  • @MyGeorge1964
    @MyGeorge1964 8 месяцев назад

    Beautiful!

  • @haithem8906
    @haithem8906 8 месяцев назад +4

    you can also set href:undefined in the button props.
    so it will be
    ButtonProps & {href:undefined}
    | aProps & {href: string}
    which will solve the problem, without adding the "as" prop

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

      Your solution won't work because it'll force you to manually pass href={undefined}.
      Perhaps you meant { href?: undefined }? Which is a good suggestion - though it has some similar issues with onClick, I think.

    • @bushiiko
      @bushiiko 8 месяцев назад +1

      ​@@mattpocockukwould it work with {href?: never}

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

      @@bushiiko { href?: never } is the same as { href?: undefined }

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

      @@mattpocockuk I think, optional never should work.

  • @edvinas396
    @edvinas396 8 месяцев назад

    Polymorphic component can come handy if it is determined by an outside source. Like Notion block.

  • @dorian0623
    @dorian0623 7 месяцев назад

    I had this idea of mimicking Material UI's typing for my polymorphic Button component, but then I convinced myself that even if I fix the type, it would still be tough to maintain and expand the existing component. I found peace of mind by creating separate Button and ButtonLink components that use a shared style factory.

  • @rafaelbernardino4615
    @rafaelbernardino4615 8 месяцев назад

    awesome tip!!!

  • @lovrozagar3729
    @lovrozagar3729 8 месяцев назад +1

    Unbelievable, I was just doing the exact same thing from the example in my project and rethinking it. 😆

  • @DaumChannelGamePart
    @DaumChannelGamePart 8 месяцев назад

    Thank you so much! I really want to know this until using mui button.

  • @0xtz_
    @0xtz_ 8 месяцев назад

    Amazing 😮

  • @attevirtanen271
    @attevirtanen271 8 месяцев назад

    I also like to make my react component state one discriminating union, and that way the state is as restricted as possible.

  • @oussama40612
    @oussama40612 7 месяцев назад

    Amazing

  • @user-kt7li4le8s
    @user-kt7li4le8s 7 месяцев назад

    Thanks! I was looking into "as" for react components... Yeah it's not a nice thing to do. Having "variants" is sort of better, but good god is it annoying to have very, very similar code in neighboring components

  • @EthanStandel
    @EthanStandel 8 месяцев назад +1

    When i do this component, I prefer to just mandate the href prop for the anchor tag. So basically, it's always a button if there's no href

    • @ra2enjoyer708
      @ra2enjoyer708 7 месяцев назад

      But then you need to make a disabled link... which is only possible with an empty href.

  • @joestrkr
    @joestrkr 8 месяцев назад

    Gold

  • @Mitsunee_
    @Mitsunee_ 8 месяцев назад

    I've had this massive supersmart button component before that also did some accessibilty stuff, forced rel="noopener noreferrer" for _blank targets, used the framework's Link component where appropriate and so on. It constantly broke. Not worth it. Just make a LinkButton and ActionButton as two separate components.

  • @dealloc
    @dealloc 8 месяцев назад +4

    For elements like anchor and button elements they should be different components, since they have different semantics (both have onClick, but anchors loses accessibility if used). I'd rather go for a compositional approach, similar to Radix' `asChild` prop.

    • @EdwinMartin
      @EdwinMartin 8 месяцев назад

      He explains polymorphism. The a and button are just examples to explain it.

    • @dealloc
      @dealloc 8 месяцев назад

      He's explaining the `as` prop with TypeScript specifically here, which is why I brought up `asChild` which uses composition as a way to provide polymorphism, without the all the typing and headaches that `as` comes with.
      I just left out that there are better examples of polymorphic components than A and Button, which would result in more useful patterns than `as` prop.

    • @kamehameha38
      @kamehameha38 7 месяцев назад

      That's right. But it can be difficult to create a Slot component yourself without using a UI library.

    • @dealloc
      @dealloc 7 месяцев назад

      @@kamehameha38 And why can't you use a component like Radix' Slot component? You don't need the whole UI library, just install the individual component.

  • @olatunjiolakunle6908
    @olatunjiolakunle6908 8 месяцев назад

    solves this problem in vue

  • @shakapaker
    @shakapaker 8 месяцев назад

    Please make guide on how to create a mega polymorphic component

  • @benjidaniel5595
    @benjidaniel5595 8 месяцев назад +2

    By the end of this video it’s actually more code for the consumer than had we used separate components with some internal composition for any common logic.
    So glad you finished this with “maybe have separate components” 👌

  • @krzysztofprzybylski2750
    @krzysztofprzybylski2750 8 месяцев назад +1

    It also sounds like "href" in my mind. Great vieo as always

  • @tomatao.
    @tomatao. 12 дней назад

    I'm fairly sure I solved this with `href?: never` on the button so that the `as` becomes optional and only needed for those who prefer it to be explicit - handy in component libraries that need links looking like buttons and buttons looking like links as the components can be agnostic to semantics while offering styles

  • @nickderaj
    @nickderaj 8 месяцев назад

    The classic buttonOrLink, I think it's easier to have two different components and within them shared styling of sorts. That way you keep the styles consistent between them but you don't have to do typescript spaghetti

  • @armanomidi4859
    @armanomidi4859 8 месяцев назад

    I have a question, when to use ComponentPropsWith(or Without)Ref or something like Rect.AnchorHTMLAttribute? its a little bit confusing :)

  • @Andres-it2du
    @Andres-it2du 8 месяцев назад +1

    This is probably the first time I don't entirely agree with Matt. I do like polymorphic components, but I also use them wisely. The problem I see with your example here is that it only accepts a standard 'a' tag or a button, if I were to pass a react-router-dom's Link component I would be toast, same with any other routing solution out there. Here's what my component would usually look like with all the types working as expected:
    import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
    import s from './button.module.scss'
    export type ButtonProps = {
    as?: T
    children: ReactNode
    className?: string
    fullWidth?: boolean
    variant?: 'icon' | 'link' | 'primary' | 'secondary' | 'tertiary'
    } & ComponentPropsWithoutRef
    export const Button = (props: ButtonProps) => {
    const { as: Component = 'button', className, fullWidth, variant = 'primary', ...rest } = props
    return (

    )
    }
    I use the same approach for other components like Typography, works flawlessly for our needs.

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

      The complexity comes in when you want to do any forwardReffing, or provide defaults to the 'as'.
      I also don't really mind the 'as', but because it's so difficult in TS I generally prefer to recommend simpler components.
      www.totaltypescript.com/pass-component-as-prop-react

    • @FirroLP
      @FirroLP 8 месяцев назад

      What Matt said, using refs with polymorphic components is an extreme PITA. It's possible, but really ugly to write and in the territory of "do not touch" code

  • @SwapnilSoni
    @SwapnilSoni 8 месяцев назад

    Crazy

  • @oliversaxon8656
    @oliversaxon8656 8 месяцев назад

    I was so thrown when you said "herr-eff"

  • @novailoveyou
    @novailoveyou 8 месяцев назад

    How would we go about forwardRef tho?

  • @personofsomething6205
    @personofsomething6205 7 месяцев назад

    DUDEEEEEEEEE MAKE A FULL COURSE ON UDEMY

  • @quintencabo
    @quintencabo 8 месяцев назад

    But why do you have a component that is either a button or an a?? Just use a button or a

  • @Pukkah
    @Pukkah 8 месяцев назад

    Reminds me of a tweet I posted a few days ago... 🤔

  • @ayushgogna9732
    @ayushgogna9732 8 месяцев назад +1

    i found this code somewhere i don't remember what do you think about this think someone at nextjs has written this
    import { ComponentProps } from 'react';
    import Link from 'next/link';
    type ButtonOrLinkProps = ComponentProps & ComponentProps;
    export interface Props extends ButtonOrLinkProps {}
    /**
    * This is a base component that will render either a button or a link,
    * depending on the props that are passed to it. The link rendered will
    * also correctly get wrapped in a next/link component to ensure ideal
    * page-to-page transitions.
    */
    export function ButtonOrLink({ href, ...props }: Props) {
    const isLink = typeof href !== 'undefined';
    const ButtonOrLink = isLink ? 'a' : 'button';
    let content = ;
    if (isLink) {
    return {content};
    }
    return content;
    }

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

      This line of code is pretty weird:
      type ButtonOrLinkProps = ComponentProps & ComponentProps;
      Not sure why you would want to COMBINE the props.

  • @vorant94
    @vorant94 8 месяцев назад +3

    now I understand what means "type gymnastics": you as a developer clearly know, that in case passing href it should be anchor, in any other case it will be button. but since "typescript doesn't know it" you have to go deep and even come up with an additional prop that is served as a discriminator... huge fan of ts, but I doubt the cases, when instead of helping it requires you to do additional work

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

      Typescript is still helping in this case. It's letting you know that you're being too "clever" and, as he said, should rethink your life choices. My code has become much easier to maintain after I started to use Typescript properly, because it doesn't let me get away with dumb stuff like this. Just make separate Button and Link components, everything becomes much clearer, both in the components themselves and in the code using those components.

  • @MrREALball
    @MrREALball 8 месяцев назад

    What if I want to forwardRef into this?

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

      Welcome to hell
      www.totaltypescript.com/pass-component-as-prop-react

  • @the-old-channel
    @the-old-channel 8 месяцев назад +2

    wouldn’t & { href: never } on button props do the trick and make a proper discriminated union without ‘as’?

  • @archmad
    @archmad 8 месяцев назад

    there is a problem with your solution, `a` does not have to have `href`. your solution forces this to be a button.

  • @rustwithoutrust
    @rustwithoutrust 8 месяцев назад

    This is an entire sequence of error-solving.

  • @ricko13
    @ricko13 8 месяцев назад

    @DHH tried to do this, 5 minutes later... "Turbo 8 is dropping TypeScript"

  • @GavHTFC
    @GavHTFC 8 месяцев назад

    You can't have an anchor without href, just like you can't have a pie without cool hwip

  • @mahadevovnl
    @mahadevovnl 8 месяцев назад

    Most applicants I see: "But what if I want to put a inside an ?" - me:

  • @omri9325
    @omri9325 8 месяцев назад +2

    And now you need to destructure or omit the 'as' property since you didn't intend for it to be in the DOM

  • @Andreas_Mann
    @Andreas_Mann 8 месяцев назад

    Oh god, I would never do this. I feel like typescript should make things easier, and when it doesn't I turn around and take a different path.
    Directives could easily solve this. Alas, it's React. 😀

  • @ayodejiikujuni1233
    @ayodejiikujuni1233 8 месяцев назад

    😂

  • @a____________________
    @a____________________ 8 месяцев назад

    yeah i'll just stick to javascript LOL

  • @SergiySev
    @SergiySev 8 месяцев назад +3

    just no, please, don't code like this

  • @babakfp
    @babakfp 8 месяцев назад

    Dude, we deserve better, why front-end is so sh*t!!!

  • @ColinRichardson
    @ColinRichardson 8 месяцев назад +60

    The first line of the code is the issue.

    • @pupfriend
      @pupfriend 8 месяцев назад +3

      LOL

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

      Implying other frameworks allow to write type-safe wrappers over html elements, let alone polymorphic components.

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

      @@ra2enjoyer708 I'm sorry, I just assumed you knew what Vue was..

    • @oussama40612
      @oussama40612 7 месяцев назад +1

      React is by far the closest-to-TS/JS framework

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

      Hur dur

  • @CottidaeSEA
    @CottidaeSEA 8 месяцев назад

    This just seems kind of useless to me, because if you already know what type you want to use, you might as well just change what component you're using. This seems like pointless work for the sake of it. Create two components, let them share styles if that's what you want, but type juggling like this is what makes TypeScript awful and you're overcomplicating things.

  • @stdDizzasTeR
    @stdDizzasTeR 8 месяцев назад

    Typescript is garbage, I'm ready for hate comments.

  • @faraonch
    @faraonch 8 месяцев назад +1

    Complicated crap like this makes me still hating Typescript. It's obvious that I will still use it over JS, as plain JS is pointless, too. Nevertheless, Typescript is still just the better piece of shit. Even most senior and quite well experience UI and JS developers never have time to learn these miss designed behavior! Me neither. Leave me alone and let me build UI's and Apps. The moment I see error messages like this, I am done. How should I fix the problem when I don't even understand the error message?!

    • @AbNomal621
      @AbNomal621 8 месяцев назад

      Don’t blame the language. Blame the devs who think this is some sort of good idea.

    • @faraonch
      @faraonch 8 месяцев назад

      I agree, somebody thinking creating such a component is needed has also lost it's mind.@@AbNomal621 I have been creating professional apps for more than 10 years and never even came close to something like this.

    • @doc8527
      @doc8527 8 месяцев назад +2

      ​@@faraonch A lot of devs do this because they are obsessed with DRY, just want to save few keystrokes instead of doing copy & paste for few lines of styling, while those styles can be extracted to accomplish the same thing without doing the ugly work around to compromise both the javascript and the typescript. In fact, typescript does the right job, it complains a ton for this simple decision you make to let you know even doing this in pure javascript is still a terrible idea. Same styles/props can do differently in different component, you might end up a terrible debug hell to mash up everything together. Ironically, this approach is also against the idea of DRY.

  • @sh8yt
    @sh8yt 8 месяцев назад

    That was hell when polymorphic component and just today I found mind blowing radix utils call react-slot that save me a lot of headache (not sponsor btw)

  • @rzr1191
    @rzr1191 8 месяцев назад +1

    link is a much better and simpler pattern

    • @ra2enjoyer708
      @ra2enjoyer708 8 месяцев назад

      Except links and buttons are not allowed to be nested within each either.

    • @dealloc
      @dealloc 8 месяцев назад

      I agree asChild is the right approach for composition. Though, in this case your underlying Link component should not be an anchor, if the outer button provides `onClick` handler to the child. Providing an onClick handler on anchors removes its accessibility that you'd expect from an anchor.