DEV Community

Amaka Mbah
Amaka Mbah

Posted on • Updated on

Understanding vue by building a country directory app part 3

Hello everyone, we'll be continuing on our article episode. I know I said I was going to post this yesterday but I couldn't because I was down with a cold, I apologize and I feel a lot better now. I'm sitting at my desk right now and excited to churn this out 🙂

Objective
By the end of this post, you should:

  • Have a good understanding of props and how the parent component communicates with the child component and vice-versa.
  • Add dark mode theme to our app

Let's establish a foundation before we gradually build up. The feature we are about to add is made possible with props, so let me introduce it briefly.

What is props?
Props simply means properties. It is data that is been passed from the parent or root component to the child. Props can be a string, function, array, boolean or object; it's basically primitive data type. It cannot be changed in a child component only where it was first declared which is the parent component. Vue refers to this act as prop mutation when you try to set it to a new value in a child component.

Let's move on.

Remember I said props are data passed from parent to child component. First, our data has to be declared in a parent component. Now, we'll head over to our App.vue file and declare our data and we do that in reserved Vue property known as the data property, the name even tells what it does. In the parent component, it is data but in a child component, it is props. Hope that made sense?

Root component (App.vue)
In the data property, we are going to set three data states:

  • modeTheme
  • modeIcon
  • modeText

The modeTheme would affect the color state of our app, at first we will be setting it to 'light' which is the default color state.

The modeIcon and modeText would only affect the Toggle component. At first the modeIcon will be set to 'fa fa-moon-o' and the modeText set to 'dark'. Both are default states as seen in our app. Armed with some JavaScript logic, we will change these states.

This is what we should have now:

export default {
  name: "App",
  data(){
    return{
      modeTheme: 'light',
      modeIcon: 'fa fa-moon-o',
      modeText: 'dark'
    }
  },
  components: {
    Header,
    Content
  }
};
Enter fullscreen mode Exit fullscreen mode

Let's begin building our app Vue Style. Next thing is we are going to bind our data to the child components. In binding data as props to a child, the prop should of course have a name and the data it is pointing to.

Header and Toggle component
First, we will take out 'dark mode' and 'fa fa-moon-o' we hard coded in the Toggle component. The prop names iconMode and textMode bind to the modeIcon and modeText data respectively. We add them into our header component tag using either v-bind attribute or its shortcut :

<Header 
  :iconMode="modeIcon"
  :textMode="modeText"
/>
Enter fullscreen mode Exit fullscreen mode

I know you're thinking, how does this even relate? If you go back to part 1 we imported the Toggle component into the Header, this made Toggle a child to Header. Toggle has an indirect access to the data in root component (App.vue) through it's own parent (Header.vue) and this is made possible through props.

We will quickly add in our props into the header vue instance and there is a reserved property name for it known as props.

export default{
    import Toggle from './Toggle'
    name: 'Header',
    props: ["iconMode", "textMode"],
    components:{
      Toggle
    }
}

Enter fullscreen mode Exit fullscreen mode

Our initial data in App.vue is binded to these prop names so don't be confused if you see iconMode instead of modeIcon, you can use either one but I prefer to use this. Now, at this point, Toggle has access to the modeIcon and modeText data. What we'll do next is, bind these props data to the Toggle tag component and declare them in the Toggle.vue file in reserved props property as we did for the Header.

First, bind the props data to Toggle component:

<Toggle
   :toggleIcon="iconMode"
   :toggleTheme="textMode"
 />
Enter fullscreen mode Exit fullscreen mode

Then, declare these prop names in the props property:

export default{
    name: 'Toggle',
    props: ["toggleIcon", 'toggleTheme']
}
Enter fullscreen mode Exit fullscreen mode

Next up, use the prop names where needed. In this case we will be replacing:

<i class="fa fa-moon-o"></i>
<span>Dark Mode</span>
Enter fullscreen mode Exit fullscreen mode

with this, making our application reactive. Reactive in the sense that, if the data in toggleIcon which points to modeIcon in our App.vue does change, it would change here too.

<i :class="toggleIcon"></i>
<span>{{toggleTheme}}</span>
Enter fullscreen mode Exit fullscreen mode

Here, we just binded our prop data to our class attribute since the data should be a class name and replaced the hard coded text with the data toggleTheme using string interpolation.

For my class bind, this is an expanded version of what I did up there. Choose whatever you like but the former is shorter BTW.

<i v-bind:class="toggleIcon"></i>
Enter fullscreen mode Exit fullscreen mode

I'm taking my time to explain it so I don't get to explain again and again. Hopefully all I've said so far made sense.

Moving on...

It's time to work on our color state. Back in our root component, the default color (here I mean both background and text color) state was set to 'light' but we want to be able to change the state from light to dark and vice-versa.

How will we make this happen?

  • We will add a click event to our Toggle component and assign a custom event through an $emit. The $emit is a way to pass data from child component to parent component through custom events.

Let's do that now:

In our Toggle component, add a click event handler together with an $emit which will point to a toggle string.

<template>
  <div class="Toggle" @click="$emit('toggle')">
    <button>
     <i :class="toggleIcon"></i>
     <span>{{toggleTheme}}</span>
    </button>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Here, I'm signalling to the parent component (Header) that there is an incoming custom event 'toggle'.

In the Header component where the Toggle component tag is declared, I will bind the custom event 'toggle' to another custom event called 'toggler' using $emit, but we are not done yet, our root component isn't still aware of the click event happening in our Toggle. Remember, we pass data from child to parent through $emit. We successfully made that happen from Toggle to Header, now we need to do same from Header to App.

<template>
  <div class="Header">
    <h2>Where in the world?</h2>
    <Toggle
      :toggleIcon="iconMode"
      :toggleTheme="textMode"
      :toggle="$emit('toggler')"
    />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now 'toggler' would do the final work of signalling the root component of the existence of a click event. It will be the custom event the root component works with. It will be declared in the fashion of all event handlers, either like this v-on:toggler or @toggler. At this point, it represents the @click event handler, which I must say is a very interesting twist.

In the Header tag component, we will alert the root component that there is a custom event representing a click event and that it carries a function called toggleIt as seen in the code block.

<template>
  <div id="app">
    <Header 
      :iconMode="modeIcon"
      :textMode="modeText"
      @toggler="toggleIt"
    />
    <Content/>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

With our toggleIt function announced to the root component, we declare it inside our methods property, another reserved space for storing functions.

What should the toggleIt function do?
This is basically where we write our JavaScript logic to control color state. Let's do that now.

methods:{
    toggleIt(){
      if(this.modeTheme === 'light'){
        this.modeTheme = 'dark';
        this.modeIcon = 'fa fa-sun-o';
        this.modeText = 'light mode'
      }else{
        this.modeTheme = 'light';
        this.modeIcon = 'fa fa-moon-o';
        this.modeText = 'dark mode'
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

I believe whoever is reading this post has a vanilla JS background and so no need to go over this. You shouldn't be getting into Vue without knowledge of Vanilla.

Now, one final thing to achieve our aim. We need to pass the data modeTheme from parent (App) to the children components by binding it through props. From our logic above, we are changing the state of modeTheme based on it's current state. So, if it's dark when I clicked, set it to light and vice-versa.

We need some way to show the state switching does work.

Header
Bind themeMode which is the name of the prop pointing to the initial data modeTheme in App to a class using the v-bind directive:

<template>
  <div class="Header" :class="themeMode">
    <h2>Where in the world?</h2>
    <Toggle
      :toggleIcon="iconMode"
      :toggleTheme="textMode"
      v-on:toggle="$emit('toggler')"
    />
  </div>
</template>

<script>
  import Toggle from './Toggle'
  export default{
    name: 'Header',
    props: ["iconMode", "textMode", "themeMode"],
    components:{
      Toggle
    }
  }
</script>
Enter fullscreen mode Exit fullscreen mode

The final result for Header and so since the themeMode prop is being binded to a class, there should be an actual class name declared in our style, this is so when my state goes from 'light' to 'dark', my 'dark' state which is a css class should kick in. This is even cooler because, we get to pass this themeMode prop across different components and decide what type of color we want for our 'dark' state.

Guys, this has been a long one. I'm glad I finally came to the end of it. This is me documenting my learning process and frankly, before I started writing, I didn't understand the whole $emit thing but right now, I can boldly say, it all makes sense now.

If it made sense to you. Do comment below and suggestions are welcome. Thank you. Next up would be populating our app with country data, that won't be tomorrow but my next post.

Link to code: https://codesandbox.io/s/country-directory-app-4byey?file=/src/App.vue

Link to demo: https://4byey.codesandbox.io/

Stay safe and byee!

Discussion (0)