DEV Community

Marina Mosti
Marina Mosti

Posted on • Updated on

Hands-on Vue.js for Beginners (Part 5)

This time around we're going to look (finally) at components! So get some ☕️ and let's get started.

Here's the clean slate for today's article 😁

<html>

<head>
  <title>Vue 101</title>
</head>

<body>
  <div id="app">

  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Component Basics

We're going to nosedive right into component creation because this is where really fun stuff starts. But rest assured that this is only barely brushing the power of components. Also, we still have yet to learn computed properties and watchers, which will also be a huge aid. But we'll cover that next time on top of what we learn today.

Components are a core part of the Vue.js framework, they are your Lego blocks for building reactive, rich applications - and luckily for us, they are super simple to learn and use!

Think of a component as any element in your webpage/app that you want to use one or several times. It can be as smol as a button, or input, or as a big as your whole menu bar or even the whole page's view.

The advantage of creating a component is that you write the logic for how its displayed (HTML/CSS), and how it interacts with the user (JS) once - and then you just use it all over your app.

As usual, we're going to start with the simplest possible example, a button.
Let's define first the blueprint or template for this component, we'll call it awesome-button and it shall be so!

Let's define a new awesome-button component. Copy this code above your new Vue declaration.

Vue.component('awesome-button', {
  template: `<button @click="clickHandler">Click me for some awesomeness</button>`,
  methods: {
    clickHandler() {
      alert('YAAAS 😎');
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

The Vue object that is made available to us through the Vue.js script tag that we added on day 1 has this component method that we are calling here. It allows us to create a new component, as expected. The first parameter we are setting is a String, which will be the name of our component.

The second parameter is a JavaScript object, which - surprise - is actually the same type of configuration object that we have been using for our main Vue instance! What does this mean for you? That you already know how to assign properties and methods for this new component.

In the example above, you will notice just one difference - the template property. In here we're declaring a String (note that we're also using the back-tick character to wrap it so that we can declare multiple lines later on if we need without having to concatenate multiple strings), this String will hold our actual HTML code for this component. In the case of this particular example, just a simple <button> tag will suffice.

If you reload your page right now, nothing will happen. Remember earlier I told you this was only the blueprint/template? It's time to actually render it on our page.

Head over to the <div id="app"> where we have been placing all our markup, and create a new <awesome-button> element.

Your HTML should now look something like this:

<div id="app">
  <awesome-button></awesome-button>
</div>
Enter fullscreen mode Exit fullscreen mode

Load the page, and now you will actually see the button is rendered on the page. Place a couple, or ten more (reader's choice), awesome-button tag on the page. Now you start to see the power of components, although at this point I think we can take it up a nudge.

Bonus: If you're the curious type, take a look at your page source and compare it to the inspect feature with your browser's developer tools. When the page load, Vue.js is using the <awesome-button> tag as a placeholder to where it should put the parsed content of our template.

Level 2 - Something a little more useful

Let's revisit our example from last week, and play some more with our games data.

First, let's re-add this array of games into our data() in the main Vue instance.

const app = new Vue({
  el: '#app',
  data: {
    games: [
      { name: 'Super Mario 64', console: 'Nintendo 64', rating: 4 },
      { name: 'The Legend of Zelda Ocarina of Time', console: 'Nintendo 64', rating: 5 },
      { name: 'Secret of Mana', console: 'Super Nintendo', rating: 4 },
      { name: 'Fallout 76', console: 'Multiple', rating: 1 },
      { name: 'Super Metroid', console: 'Super Nintendo', rating: 6 }
    ]
  }
});
Enter fullscreen mode Exit fullscreen mode

Just as before, feel free to update these with your favorite titles.

This time, however, we are going to create a game-card component, that will make a bit more sense to display our data in.

Ready for this?

Vue.component('game-card', {
  props: ['gameData'],
  template: `
    <div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;">
      <h2>{{ gameData.name }} - <small>{{ gameData.console }}</small></h2>

      <span v-for="heart in gameData.rating">❤️</span>

      <button @click="increaseRating">Increase Rating</button>
    </div>
  `,
  methods: {
    increaseRating() {
      // this.game.rating++ ?
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Don't get overwhelmed, you already know all/most-all of this 😃!

We're creating a new Vue.component and naming it game-card. Let's skip props for a second, and look at the template.

Nothing new here, except you may have noticed we're accessing the properties of a gameData property that is not defined inside data, but inside the props property.

Afterward, we declare our methods object, with the increaseRating method inside it. I've purposely commented out this.game.rating++ which would be how you may want to address this particular function, but it won't work! Now it's time to talk about props.

Component Props

One of the reserved properties we can have on our custom components is called props. In its simplest form, it will take an array of Strings that will define variables. In our previous example, we are telling the component blueprint/template that we want it to be aware of a property called game.

Props will allow us to pass information into our components from outside! Let's view this in action, it'll be easier to grasp.

First, let's add a bunch of <game-card> items to our app. We will be using a v-for loop just like we did before, but we're going to loop on top of our custom components this time!

<div id="app">
  <awesome-button></awesome-button>
  <hr>
  <game-card v-for="game in games" :game-data="game" :key="game.name"></game-card>
</div>
Enter fullscreen mode Exit fullscreen mode

That is quite a bit of game being tossed around, so let's look at it in detail.

First step, we are creating our <game-card> component, like we discussed earlier.

After, we add the v-for="game in games" loop like we saw last week. This creates a game variable that will hold the current game in the loop, and we can use it right away!

Finally, we assign to the template's prop, gameData, a value, in this case our game variable from the loop. Notice that instead of camel case, we're using a hyphen game-data because HTML is case insensitive. If you are having a hard time grasping this, try thinking it in object terms. We are doing something similar to game-card.props.gameData = game

Don't forget about the :key!

There is a huge gotcha to mention here, we are passing game to our game-data prop, but there is a : behind it. Did you notice?

When we assign a property to a component instance, there's two ways to go about it. Following our example, we could either do it with the : before it (this is a shorthand for v-bind:!). This will make sure the data that we are passing after the ="<here>" is used by JavaScript as a variable, or an actual piece of code.

If you were to type instead gameData="game", then Vue will take this as assigning the gameData prop the String "game". So something like: game-card.props.gameData = "game"!

Go ahead and take a break from theory and actually go and run this in your browser. You will see as expected, that our whole <game-card> component's template is being rendered for each one of our game's.

The greatest part about this is that if we were to make a change to our HTML, it will be updated everywhere in our app.

Also, and most importantly, components allow you to contain the logic for that particular component. Let's revisit that game-card's increaseRating() method.

Component Data vs. Props

Props for components actually can be a very lengthy subject, but there is a very important rule of thumb that you must always keep in mind. A property should NEVER be modified from inside a component.

In fact, if you try to do this, Vue will throw all sorts of warnings and yell at you in the console - because this WILL lead to unexpected behavior. Here's the documentation, in case you want to read about it: Mutating props in Vue2 is an anti-pattern.

How then, will we modify the value of rating inside of our component's storage? The key is in the question itself! We need to make a copy of this prop into our data so that we can actually modify it.

Let's first add our data to our game-card component, and assign it a new non-conflicting name (props and data props will conflict if named the same), and then pre-fill it with the prop's value.

data() {
  return {
    game: {...this.gameData}
  }
},
Enter fullscreen mode Exit fullscreen mode

Couple of things to note here, but before that, if you don't know yet what {...gameData} is doing, it's a spread operator. I won't go into full detail here and will try to post a brief article about it soon, but basically were making a copy of the gameData prop, because we don't ever want to modify that from the child.

The data property's return:

When we learned about the data property, I told you that it needed to hold an object with all the properties we needed, and this is how we've been doing it for our main Vue instance. However for components we actually need to make it a function, by adding (), and second return the actual object.

But WHY?! 😫

Simply put, there can be one or many instances of your component, right?

Each instance will need a unique data object! You don't want to share a single data object between all of those, or they would all share the same title for example - and the point of this whole app would be lost.

So the whole reason behind making it a function and returning an object, is that Vue can ☎️ call this function every time it creates one of our game-cards. That way each one will get a unique object of data to play with!

Accessing our props:

When we create the game's data property, we are assigning it this.gameData, so a couple of things to learn here. props can also be accessed within your component scripts via this just as your local state props from data. So here, we are setting game to be equal to the gameData property.

This means now we have to update our HTML, so inside the component switch the gameData reference for game like so:

<div style="border-radius: .25rem; border: 1px solid #ECECEC;">
  <h2>{{ game.name }} - <small>{{ game.console }}</small></h2>

  <span v-for="heart in game.rating">❤️</span>

  <button @click="increaseRating">Increase Rating</button>
</div>
Enter fullscreen mode Exit fullscreen mode

Run this once again in your browser, and you should get the same results.

Finally, we are at the point where we can make our increaseRating method work! Head to the method and replace the comment with the following:

methods: {
  increaseRating() {
    this.game.rating++
  }
}
Enter fullscreen mode Exit fullscreen mode

With every click, we're going to increment the component's internal data property which holds the game's rating, -not- the prop.


There is a lot of theory to be learned about components, i've just began to scratch the surface, but hopefully you are starting to have a clearer picture of why frameworks like Vue are so popular, and so much fun to use.

From this point forward we're going to start looking at what I consider intermediate topics, like computed properties, watchers, events, etc. So hopefully you are excited to get to know the meaty part of Vue soon enough.

In case you need it, here's the complete code for today, and thanks for reading! 🤗😋

<html>

<head>
  <title>Vue 101</title>
</head>

<body>
  <div id="app">
    <awesome-button></awesome-button>
    <game-card v-for="game in games" :game-data="game" :key="game.name"></game-card>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

  <script>
    Vue.component('awesome-button', {
      template: `<button @click="clickHandler">Click me for some awesomeness</button>`,
      methods: {
        clickHandler() {
          alert('YAS. 😎');
        }
      }
    });

    Vue.component('game-card', {
        props: ['gameData'],
        data() {
          return {
            game: {...this.gameData}
          }
        },
        template: `<div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;">
          <h2>{{ game.name }} - <small>{{ game.console }}</small></h2>

          <span v-for="heart in game.rating">❤️</span>

          <button @click="increaseRating">Increase Rating</button>
        </div>`,
        methods: {
          increaseRating() {
            this.game.rating++
          }
        }
      });

    const app = new Vue({
      el: '#app',
      data: {
        games: [
          { name: 'Super Mario 64', console: 'Nintendo 64', rating: 4 },
          { name: 'The Legend of Zelda Ocarina of Time', console: 'Nintendo 64', rating: 5 },
          { name: 'Secret of Mana', console: 'Super Nintendo', rating: 4 },
          { name: 'Fallout 76', console: 'Multiple', rating: 1 },
          { name: 'Super Metroid', console: 'Super Nintendo', rating: 6 }
        ]
      }
    });
  </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Latest comments (22)

Collapse
 
rodz profile image
Rodrigo Gomez

Interesting how Vue keeps the reactivity intact but with added costs and concepts with components. The semantics changed from data: {} to data(){return copy{}}.

Collapse
 
jyotik09 profile image
jyotik09 • Edited

Hi Marina,

Kudos to you for this super simple and insightful blog...
I came across this when i was searching for something in Vue.js and this really intrigued my quest to learn more about Vue !! :)

Collapse
 
marinamosti profile image
Marina Mosti

Hey Jyotik09, thanks for your comment :) I'm really glad you liked it! Vue is a really fun framework to work with

Collapse
 
pravinkumar95 profile image
Pravin kumar

Nice tutorial. Got a little bit confused with game-data and gameData. Eventually got used to it. Great Work!

Collapse
 
dragz profile image
Roy Dragseth

Hi Marina. I can only join the praise you get for this tutorial. It is one of the best I've read in a long time. Thanks!

Two comments on the code example in this section:

I was slightly confused until I discovered the implicit conversion from camelcase to kebabcase and thus did not understand that 'gameData' in the component declaration corresponds to :game-data in the game-card tag.

Also, the game variable name is used in different contexts. Initially I thought it had to be the same in both the v-for loop and the component declaration, but after toying with the source code it became clear that game in data() is unrelated to the game looping index in v-for. (Pretty obvious in hindsight)

Both these things might be part of the folk-wisdom, but for a newbie like me it threw me off the track for 15 minutes.

Again, thanks for a great tutorial!

Collapse
 
marinamosti profile image
Marina Mosti

Roy, thanks for your kind words :) Nice catch! It's sometimes hard to look at all the angles where someone can trip on, but your input is super valuable. Hopefully, it also helps more people that got tripped up by the same thing

Collapse
 
frfancha profile image
Fred

Hi Marina,
First a big thank you for this blog that I follow with great interest every week.
Could you elaborate a little bit on the change of prop in component?
Because the anti-pattern link that you give explains why it is a bad idea (and actually now forbidden) to change the value of the property itself in the component, it doesn't really speak about changing attributes of the property.
Which is what we would have done here by doing gameData.rating++ instead of game.rating++. We wouldn't have changed the property completely by doing gameData = {...}
?

Collapse
 
marinamosti profile image
Marina Mosti

Hey Fred, thanks for reading :)

When you change the attributes in an object or array based prop it will still trigger a re-render on the parent, the change is also untracked and will not emit an event because it's not being listened for.

Any changes to props directly are affecting two components, parent and child at the same time so the pattern of data delegation and communication is effectively broken. The parent will re-render and trigger overwrites of the data. In some cases it will "seem" like this is not a problem, but in parent/child relationships where a lot of data is being transferred or when the parent has a lot of re-rendering happening because of state changes you are going to get yourself a free headache and very little ways to debug it.

This is actually a GREAT question, but TLDR mutating props is a huge no no - data should only be flowing one way :)

Collapse
 
frfancha profile image
Fred

"mutating props is a huge no no"
Thanks for this.
We have a huge home made AngularJS application.
One of the base component of that is a "FieldEditor" directive.
Getting 2 props: dataRow and fieldName.
The fieldEditor component allows the user to edit the corresponding field of the dataRow.
E.g. in the parent having the row "Customer" with attributes "FirsName", "LastName", "Age", you would find3 FieldEditor in the html, 1 getting Customer + the string "FirstName" to edit the first name, another one for last name and a third one for age. Note this is an overly simplified version.
I was hoping to use Vue.js as replacement for AngularJS ... Apparently not a good idea?

Thread Thread
 
marinamosti profile image
Marina Mosti

I dont think this issue is related to the framework, but more on how you structure our components. The parent should "feed" data to the child, and the child should "tell" the parent when that data is modified through some sort of event or shared state.

Thread Thread
 
frfancha profile image
Fred

Hi Marina,
Hoping you will have the time to give yet another answer...
Sorry for coming again on this topic but this is quite important for us.
Here a mini-mini codepen showing what I mean:
codepen.io/frfancha/pen/gErWKG
In this pen you can edit the name (Joe) or the age (23) in any of the "field" component in the other one is updated, so "modifying" the prop properties (not the prop reference itself) seems working perfectly in Vue.js
So we can can still use our basic building block "field" component of our big application.
(PS our "field" component does much more than the code of the pen, e.g. when the field to edit is numeric it automatically provides a calculator and other such features)

Thread Thread
 
marinamosti profile image
Marina Mosti

Hello Fred, the fact that it "works" doesn't mean that it is correct. You will run into issues eventually when the parent starts re-rendering. The correct way to handle input wrapper components is by using a value prop and $emit with an input event so you can v-model on the parent.

vuejs.org/v2/guide/components-cust...

Collapse
 
ericstolzervim profile image
Eric Stolzervim

Thanks for this lesson.

When you increase the rating, it increases the copy (not the rating in games). So when I open another browser, I don't see the rating changes that the first browser made.

How do I change the rating in games?

Collapse
 
marinamosti profile image
Marina Mosti

Hey Eric! Well, the status of your component is not going to persist through browsers unless you're storing it somehow on a server and then authenticating a user, this is way beyond the scope of these tutorials.

In order to change the parent's data you need to communicate to the parent of the component that the child's data has changed. You can accomplish this with either events or vuex (or some sort of state management).

I will eventually get to this in the tutorials, but if you want to try to skip ahead take a look at the docs and read about $emit and how to fire an event from your child component. You will also need to use a watched property as well, so this is kind of advanced in regard to what we have covered

Collapse
 
glennmen profile image
Glenn Carremans

Hey I am loving this series so far! I have always wanted to get in Vue and have learned so much already with these weekly tutorial ❤️

Btw in the last code block I think there is a mistake:
game: {...this.gameData}
shouldn't it be game: {...this.game-data}, ?
Or did I miss some part 🤔 I think the error happened because at some point you switched from gameData to game-data .

Collapse
 
marinamosti profile image
Marina Mosti

Hi Glenn! Thanks so much for your comment :)

You are absolutely right! Somehow when I copied over the whole result I forgot to update that on the examples above. Nice catch! Also, thanks for letting me know :)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.