This Vue 3 Component Pattern is Fire 🔥🔥🔥 (and you should use it!)

Поділитися
Вставка
  • Опубліковано 25 лип 2024
  • Vue has some really neat component patterns. Let's take a look at a common one how it deals with state. In this tutorial we'll look at the controlled props pattern. This pattern uses props and allows you to easily reuse complext components in Vue. We''ll also look at defineExpose
    👉 Check out my last video on Vite tips!
    • These Vite Mistakes Wi...
    👉Sign up for my mailing list and get neat stuff!
    bit.ly/3Umk7sW
    👉 Need some help with a project, level up your skills, React, Next, Vue, or Nuxt? Check out my 1-on-1 mentoring!
    mentors.to/erik
    Links:
    michaelnthiessen.com/controll...
    0:00 Patterns in Vue
    0:23 What this app does
    01:28 Refactor to use hidden prop
    03:18 A look at defineExpose
    06:18 Why you may not want to sue defineExpose
    07:05 Controlled Props (thanks Michael)

КОМЕНТАРІ • 60

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

    What patterns do you use?

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

      This can also be accomplished with the new `defineModel` macro. Try this:
      ```

      Child Toggle Button
      Child: {{ toggle }}
      const toggle = defineModel("toggle", { default: false });
      ```
      ```


      Toggle Parent Button
      Parent: {{ toggle }}
      const toggle = ref(false);
      ```
      The nice thing about `defineModel` is that it creates a local variable and emits on change. If you make it optional with a default, and don't bind it from the parent, then it acts like a ref inside the component.
      I've dealt with this pattern for years, and it always annoyed me how much boilerplate it required. This macro is great!

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

      Wasn't aware of defineExpose - nice api! 😊
      In the use case presented in the video I usually use defineModel so it can optionally be controlled from the parent.

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

      I use two-way binding watcher pattern.
      Essentially I emit the hidden value not the click event and update the model value (_hidden) which comes from the props. On the parent component I can have a hidden boolean ref, which I can control from the parent and and pass it to the child via v-model:hidden="hidden" prop. The child will set the value of _hidden boolean ref to the props.hidden value initially as a default, and then the _hidden boolean ref is fully managed internally but can be controlled externally by adding a watch on the prop, so if the props.hidden value changes from the parent, it will set the internal _hidden ref value to it.
      That way v-model sets the value of hidden on parent and the parent can control the child, and the child can control it and emit the value back up to the parent, and hence hidden is in sync on both parent and child, and can be controlled by parent and child.

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

      ever since defineModel came along in 3.3, ive been using that.

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

      @@mrrolandlawrence Yea I completely forgot about defineModel, but all-in-all that is essentially what I do.

  • @henrique-work
    @henrique-work 5 місяців тому +10

    There is also a way to do that with:
    const hidden = defineModel('hidden', { local: true })
    This would make that if there is no v-model used, the ref would be a local ref value instead
    Probably is the same thing under the hood but is a nice shortcut.

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

      Yeah, defineModel ftw.

    • @gveresUW
      @gveresUW 19 днів тому +1

      Yea, the entire time I was watching the video, I was thinking, why aren't you just using defineModel???? This is the way it is supposed to work.

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

    In defineExpose example instead of
    `ref()`
    set type as
    `ref()`
    And typescript should already know that you have available `hidden` prop of that component and you won't have to do all these unnecessary type assertions in toggle function.
    Although I think proper typing for refs of components is:
    `ref`
    But it's not giving automatic completition, even though when you hover over the ref variable it shows that `hidden` prop is available. Idk why and I dont want to deep dive on this

    • @marco.arruda
      @marco.arruda 4 місяці тому

      I also have been using like this and it's amazing, typescript detects the exposed properties! Long life to Vue!

  • @AhmedSalah-xm9xu
    @AhmedSalah-xm9xu 5 місяців тому +3

    Hey Erik,
    just wanted to mention, that you can also use the defineModel function and use a named v-model to Exposé the hidden value and inside the component you can just use a default value for the hidden attribute
    Great video to show the possibilities vue offers.

  • @Andrey-il8rh
    @Andrey-il8rh 5 місяців тому +6

    Suggestion: never use click as a name for custom event. Not only it's misleading but it also interfere with native browser events

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

    Nice tips Erik this helps me a lot at my actual project!

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

    This was kinda cool! Thanks for the idea! I usually use the emit approach and then manage the state on the parent component..following the usual flow that Vue seems to like more. But this one, even if it feels a little bit strange, looks cool! Will have to try it! Thanks Erik!

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

    Wow I've been doing this for years already, it's kind of validating to see someone like you approve of it lol

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

    Love this kind of content Eric. I'd be interested in seeing more of these in the future. 🙂
    I find myself wanting this type of feature baked-in. Makes me wish I could define these types of things directly within the prop definition. Like having a collocated function prop with a default function. ( maybe this is possible and I just haven't imagined how yet. 😅

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

      You can use the new defineModel macro to do this. If you make the model prop optional (with a default) in the child, then it behaves like a local ref value when the parent doesn't bind to it. It's far less boilerplate with the macro.

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

    Thanks for video. How can i use defineProps the way showed in video? With destructuring and props keep being reactive.

  • @Andrey-il8rh
    @Andrey-il8rh 5 місяців тому +6

    Not a single word was said about v-model and scoped slots which would be way better patterns than expose or other options shown

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

      v-model is nothing more than emit. Tbh it’s good for primitives, if you’ve to update an object it might cause issues. I’d say defineExpose might be handy and is quite uncommon AFAIK

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

    Hey, what if my child component has a form, but the submit button is on the parent component, and I want the data to come to the parent component when the form button is clicked? How can I do this? I know I can achieve this with defineExpose, but is there a better way?
    Because what if we have multiple components that contain forms? In order to get data from them, I would have to expose them in each component and also create multiple template refs

  • @gazorbpazorbian
    @gazorbpazorbian 28 днів тому

    I've been using a pattern using the mitt library and emit events with the information needed, that way the component doesn't need any prop because you can pass them in the event emission. and the mitt library weights next to nothing.

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

    how about to way binding that or 5 component deep???

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

    If the drop down is going be all over, sorry I'm just going to reach for pinia to handle its state

    • @Andrey-il8rh
      @Andrey-il8rh 5 місяців тому

      Why do you need Pinia instead of a regular composable?

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

      @@Andrey-il8rh I guess I'm assuming the project is already big in scope and a state manager is already in place. ;)

    • @Andrey-il8rh
      @Andrey-il8rh 5 місяців тому

      @@worldofwarplanesgameplay imo, even if project has Pinia set up it doesn't mean every single entity should be implemented via it. Small things should remain small

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

    it's great way to use props and control it, how about using provide inject??

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

    I like this idea, and I understand how to but I think it may have been beneficial to show how to wrap that idea into a composable to make it easier to set up. If you had multiple values to share, replicating the computed and the functions and stuff multiple times would get messy. Would be a nice use for a composable to create it for us

    • @Andrey-il8rh
      @Andrey-il8rh 5 місяців тому

      I don't agree. Creating compostable for such a trivial task would be an overkill. I've been working in projects where devs used composables for almost everything and it turned into real composable hell

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

      @@Andrey-il8rh while I do agree that composables can be overused, my point was that if there were multiple values that you were doing this with, a composable would be nice to have, as opposed to creating a ref, a computed, and a function to handle toggling it for each value. If you're only doing this one time, absolutely agree with you that a composable is overkill and not necessary. But if this is something you do often, it saves a lot of time and effort generating a bunch of repetitive code, and cuts down on the possibility of typos and errors writing those things over and over.

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

    I like define expose solution more. I'm mostly doing backend, but I'm using nuxt for a personal project

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

    Wouldn't you simplify it doing
    const hidden = defineModel('hidden', {
    type: Boolean
    default: false
    })
    Then you can update the value however you want and if the parent want to pass it down they just do v-model:hidden="hidden" or
    ?

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

    I would also use the last pattern.
    I even forgot that exposing values to the parent is possible. It is a bizarre feature.

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

    Only emits, defineExpose seems a bit too leaky perhaps although it allows a similar root behaviour.

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

    I think defineExpose we could use so little as we can and we have to understand that "I really could to use defineExpose in this case". because it's not obvious and brings a magic, that is not always easy to unravel. But I really hard want to know: how I can destructure props with required option, because const { foo = 'someValue' } = defineProps({ foo: { type: String, required: true }) isn't reactive :)

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

      That’s a part of the experimental destructure feature I had turned on

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

      @@ProgramWithErik but in vue 3.4 it's a stable version now, but i don't understand why we can't set require option for this. It's a good feature, but work... not a whole functionality of the props api - just a part: default value and... that's all (if we talk about JS and not TS)

    • @Andrey-il8rh
      @Andrey-il8rh 5 місяців тому

      ​​@@Aruta90because it's just one way of doing it and yes, it's still experimental, RFC isn't finalized. You can only benefit from such syntax with TS. If you using JS you probably should use old syntax

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

    good ideas, to know and keep in mind, thank you 👍 but I think the first option would be more like "better not do this, it's kind of bad practice", and not "you should use it!" as it adds kind of magic and more you write such code, less readable it becomes and it's better not to have such a habit 🙂but yeah, it's just my opinion

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

    isn't destructing props make it lose it's reactivity ?

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

      Was fixed in the latest version of vue3

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

      They also added defineModel which I use that in scenarios like this. where I want the prop to still be controlled by the child even if it wasn't passed in by the parent. Not sure if it's the best approach tho

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

      I forgot to show that on camera , but that’s an experimental feature I had turned on in the vite config

  • @igor-telmenko
    @igor-telmenko 5 місяців тому +1

    I think you can just implement v-model for your component instead of this. We can use :value="defaultValue" or v-model="valueDefinedInParent". Why not?

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

      This would be particularly well suited to the new `defineModel` helper since the child's value would just be a ref.

    • @igor-telmenko
      @igor-telmenko 5 місяців тому +1

      You are right. Since Vue 3.3

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

    Using `defineModel` you can get rid of all the yucky logic in the child component:
    `const visible = defineModel('visible', { type: Boolean, default: false })`
    If the parent provides the `visible` prop then it's automatically reactive to parent changes, but if not, it just acts like a local ref.

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

    cant we just target the child via refs and toggle the method? the show is in the child making it ""private" just exposing the method

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

      That’s what I did with defineExpose

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

      @@ProgramWithErik so same principle just a different name

  • @Andrey-il8rh
    @Andrey-il8rh 5 місяців тому +11

    I should say all approaches shown in the video are antipatterns. Golden rule of a great component architecture is incapsulation, if you need to expose something from a child to parent most probably you did something wrong, step back and ask yourself why am I trying to over engineer.

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

      No :)

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

      Nice theory, but in a complex real world app if you want to get a good UX sometime you need to control some aspect of a child from the parent.

    • @Andrey-il8rh
      @Andrey-il8rh 5 місяців тому +3

      @adammenczykowski it's not a theory, it's a discipline. Those cases you referring to is no more than 1%, other 99 is just an inability of many developers to build a clean and simple architecture. Also I don't think you formulated the statement in the correct wording. Controlling the child from parent is not what's discussed here, it's the other way around, when your child component tries to know and modify something in the parent.

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

    nice tips but useless in a big scale project... that is too much lines to consider 😂😂😂