One place for hosting & domains

      Vuejs

      How To Debug Components, State, and Events with Vue.js Devtools


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      When debugging a Vue application, it is common to iterate on your code, making changes then restarting the server to see if they fixed the problem in your browser-rendered site. But moving between your source code and your browser can be a time-consuming process. To make debugging more efficient, you can use a browser extension like Vue.js Devtools. Vue.js Devtools is a browser extension for Chrome and Firefox and a stand-alone Electron app created by Guillaume Chau and Evan You of the Vue.js Core Team. It provides the developer with a visual representation of the inner workings of their Vue application so that you can inspect components, data, computed properties, and events from the browser, as well as Vuex actions, getters, and mutations.

      With Devtools, you will be able to see exactly when an event is executed and what payload it has. In addition to events, you will also be able to see all of your components displayed in a DOM-like format where you can analyze component properties, such as data, computed properties, and props.

      In this tutorial, you will set up a sample Vue application, install Vue.js DevTools in your browser, then add new features to your app while testing them with the browser extension. This will serve as a first step to debugging your code with Vue.js Devtools.

      Prerequisites

      Step 1 — Setting Up the Example Application

      In this step, you will put together a sample application that you can use in later steps to try out the Devtools browser extension. To get started, generate a new Vue application via the Vue CLI. To do this, open your terminal window and run the following command.

      • vue create favorite-airports

      When prompted with the setup options for your Vue app, select Manually select features. Then select Choose Vue version, Babel, and Vuex. Once selected, hit the RETURN key and continue filling out the prompts as follows:

      Output

      Vue CLI v4.5.15 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex ? Choose a version of Vue.js that you want to start the project with 3.x ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No

      Once the favorite-airports project is created, open your terminal window and cd into the favorite-airports root folder. Once you’ve changed into the directory, create a new directory with the mkdir command:

      In your text editor of choice, create and open an data/airports.js file and add in the following:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY',
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          abbreviation: 'MSY',
          city: 'New Orleans',
          state: 'LA',
        },
        {
          name: `Chicago O'hare International Airport`,
          abbreviation: 'ORD',
          city: 'Chicago',
          state: 'IL',
        },
        {
          name: `Miami International Airport`,
          abbreviation: 'MIA',
          city: 'Miami',
          state: 'FL',
        }
      ]
      

      This is an array of objects consisting of a few airports in the United States. In this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. You will use this data to explore events and dispatches in Vue.js Devtools.

      Save the airports.js file, then create a single-file component (SFC) with the name AirportCard.vue. This file will live in the components directory of your project, and will contain all the styles and logic for the airport card.

      Create and open components/AirportCard.vue in your text editor and add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a “card”. The :first-child and :last-child are pseudo selectors that apply different styling to the first and last p tags in the HTML inside the div with the class of airport. In addition to that, you may also notice that this component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component.

      Save and exit from the file.

      Before wrapping up the setup, replace the existing App.vue component with the following code:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      p,
      h3 {
        grid-column: span 3;
      }
      </style>
      

      This code contains a v-for loop that iterates through the airports.js data and renders a series of AirportCard.vue components with airport data passed in via the prop :airport. Save this code and return to the command line.

      With the project set up, run the local development server using the npm run serve command in your terminal:

      This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080 to see the following:

      A list of cards rendered in Vue using the v-for directive.

      Your example application is now successfully set up. Throughout this tutorial, you are going to use this application to explore the Devtools browser extension.

      In the next step, you are going to install the Vue.js Devtools on Chromium and Firefox browsers.

      Before using the Vue.js Devtools, you will first have to download the program to your computer. This step will focus on installing Devtools in either a Chromium-based browser or Firefox.

      Note: Before you move on, it’s important to note that the example project that you set up in Step 1 is using Vue.js 3. This means, in order for you to use Vue.js Devtools with this newest version of Vue at the time of publishing this tutorial, you will need to install the Beta channel version of Vue.js Devtools.

      Installing on Chromium Browsers

      If you are using a Chromium browser, you can download the Devtools as a Chrome extension. The more common Chromium browsers are Google Chrome, Microsoft Edge, and Opera. If you are using other browsers such as Brave or Vivaldi, this step will work as well.

      To install, first visit the Chrome Web Store. In the search bar in the top-left, search for “Vue Devtools”. You will find a number of results, as shown in the following image:

      The Chrome Web Store search results for

      Make sure you only install Devtools that are offered by Vue.js or “vuejs-dev”, since these are maintained by the official team and are the most trusted solutions. Since you’re app is using Vue 3, you must download the Beta channel offered by “vuejs-dev”.

      Once you click on that, you can view additional information about this extension, including screenshots and an overview, as shown in the following image:

      Description page in the Chrome Web Store for Vue.js Devtools, showing a screenshot of the tool and a blue

      When you are ready, click on the Add to Chrome button in the top-right of the browser. Your browser will then download and install the extension.

      Installing on Firefox

      If you are using Mozilla Firefox, visit the Firefox Extension Store. In the top-right of your browser you will see a search bar. In it, search for “Vue Devtools”, as shown in the following image:

      Firefox Browser Add-Ons page, with

      This will show a number of search results, including an entry published by Evan You:

      Search results for

      Click on the extension that is from “Evan You”. From here, you can view additional information about the extension, including screenshots and an overview:

      Vue.js Devtools page on the Firefox Add-Ons site, with rating information, a screenshot, and a blue button labeled

      Click on the Add to Firefox button to download the extension and install it into your browser.

      Note: If you are using an unsupported browser, such as Safari for example, you can still use Vue.js Devtools as a standalone Electron application via npm. Vue’s DevTool documentation has a page about installing the Devtools as a standalone app to debug an application locally or remotely.

      You now have the Devtools extension installed to your browser. You will see the Vue.js logo where your pinned extensions show up in your browser bar. By default, this logo will be grayed out. But if you visit an application or website that is using Vue, that logo will become colorized with the Vue logo colors.

      In this section, you successfully installed the Devtools on your Chromium or Firefox browser. In the next section, you are going to take a dive into the user interface of the application to view the properties of your AirportCard components from the browser.

      Next, you will navigate through the Devtools interface to inspect the properties of the components in your sample application.

      In Step 1, you started your project on a local development server at localhost:8080. Navigate to that URL in your browser, then right-click or use CMD+ALT+I on a Mac or CTRL+ALT+I in Windows to open your browser inspection tools. In the top bar of your inspector window, there will be a new tab titled Vue. Click on that tab and the Devtools will initialize:

      The Vue tab highlighted in the inspector window.

      Vue.js Devtools initially has two sections: a listing view and a detail view. The listing view contains a list of Vue components or Vuex namespaces. When the Devtools start up, you will see a list of components on the left. Since there are a total of six airport cards displayed in your sample application, there will be six AirportCard components displayed in the listing view, along with the App component.

      In the listing view, select the first App component. The detail view will now provided extra detail about that component. In this case, you will find all of the data and computed properties associated with App.vue, which in this case includes setup with an array of objects representing the airports in airports.js.

      Now click on the first AirportCard directly under App. You will find all of the data and computed properties associated with that instance of the AirportCard.vue component. In this case, it is the CVG card, so it will show properties like abbreviation: "CVG" and state: KY. When hovering over this component, Devtools will also highlight the rendered component in your browser:

      Vue Devtools with the first component selected in the listing view, the component's properties displayed in the detail view, and the component highlighted in the browser display.

      This section showed some basic navigation of the Devtools interface, allowing you to check the properties of components generated in your Vue app. In the next step, you are going to make some changes to the Vue application via the Devtools. With these small edits, you will see how Devtools can be used to test changes to your components.

      With Vue.js Devtools, you can change the properties of your components from the browser, without needing to change your source code. This allows you to debug your application more quickly, without having to adjust your actual code. In this step, you will try this capability out by adding some conditional styling to your AirportCard components and testing it in the browser.

      Currently in your application, there are six different airports. You are going to add some code that will change the color of the card if the airport is under construction. To do this, you will need to navigate to the AirportCard.vue component. You could navigate to it manually, or you can leverage Vue.js Devtools to open this file in your text editor of choice.

      In the browser, with the Devtools window open, click on the crosshairs in the top-right of the development tool. When that is clicked, you can hover over the various airport cards. Devtools will highlight the component in a green color, indicating that the component is selected. When highlighted, click on the card:

      Vue.js Devtools open, with the

      Now that you have your component selected, click on the icon in the top-right that looks like a square with a diagonal arrow. This icon is typically used to notify the user that something will be opened outside of the application. Clicking this will open AirportCard.vue in your editor. With your component file opened, go ahead and add the following highlighted code:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}, Under Construction? {{ airport.construction }}</p>
        </div>
      </template>
      ...
      

      This will render the data within the construction property of the airport object. With that complete, add data to the src/data/airports.js file to show if an airport is under construction or not:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          ...
          construction: false
        },
        {
          name: 'Seattle-Tacoma International Airport',
          ...
          construction: false
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          ...
          construction: false
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          ...
          construction: false
        },
        {
          name: `Chicago O'hare International Airport`,
          ...
          construction: false
        },
        {
          name: `Miami International Airport`,
          ...
          construction: false
        }
      ]
      

      Save and close this data file.

      With your data added to your application, you will now write some CSS to add a class if the construction property is true. Open the AirportCards.vue component to add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" :class="{ 'is-under-construction': airport.construction }">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}, Under Construction? {{ airport.construction }}</p>
        </div>
      </template>
      
      <style scoped>
      ...
      .airport.is-under-construction {
        border-color: red;
        color: red;
      }
      </style>
      

      On the <div> with the airport class, you are binding an object to the class attribute. The key of this object is the class that will be applied, and the property is the condition that needs to be met before applying the class. The CSS in the <style> tags changes the border and the text color of the card to red if the class is-under-construction is applied to the card.

      Now open your browser and open the Vue.js Devtools. In the left pane, highlight the <App> component. On the right column, the setup method and its properties will be displayed. You will find an array of airports. Expand that array, then expand one of the objects in it. Open the first object with the abbreviation CVG. To test if this code works, click on the pencil icon of the construction data property and change it to true.

      Vue Devtools opened to modify the CVG card to have a true value for construction. The corresponding card in the browser window is red.

      Once you press ENTER or click on the save icon, that data will be updated in the frontend. The card will immediately change to red. If you were to inspect the element and review the DOM, you would find that the is-under-construction class has been applied to the first airport card.

      Now that you’ve tried out changing properties in Devtools to test your application logic, you will next see how to test emitted events from the browser.

      In addition to debugging a component’s data and properties, you can also use Devtools to debug built-in and custom events. Vue.js Devtools provides tools for visualizing events and their payloads.

      To try out testing an event, you will first add an event to your AirportCard component. When you click on an airport card, the app will emit an event to the parent component (App.vue) with the airport data that you clicked.

      Open your text editor, and in the AirportCard.vue component, add the following click event with an $emit on the outermost <div>:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" :class="{ 'is-under-construction': airport.construction }" @click="$emit('favorite-airport', airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}, Under Construction? {{ airport.construction }}</p>
        </div>
      </template>
      ...
      

      On the outermost <div>, you are invoking a JavaScript click event. In that click event, you are firing off a custom event called favorite-airport. The payload for that event is the data associated with the airport variable from the v-for loop.

      Save the changes to your AirportCard component.

      Next, open up Vue.js Devtools in your browser. In the top bar to the right of the compass icon (inspector) is the timeline inspector. Click on the icon to open up the Devtools timeline:

      Animation showing a mouse cursor clicking the event timeline view in Vue.js Devtools.

      This timeline inspector lets you inspect any event, keyboard stroke, mutation, and more that happens over a period of time. With the tools open, click on one of the airport cards in the browser viewport. Once you do that, you will see a series of dots on your timeline:

      Vue Devtools event timeline view showing a series of purple and green dots in lines parallel to each other.

      These dots represent various events that happen behind the scenes, including the mousedown and mouseup events that were executed on your click. Mouse events will always be highlighted in purple. The green dot is your custom event that you set up to fire on the click event. Click on the green dot to find more information regarding that event, as shown in the following image:

      Two extra panes of the Vue.js Devtools timeline view that show a log of component events and information about the event, including its name, component, and parameters.

      In this pane, you can see when the event was executed, as well as the data that was passed up to the parent. In this case, the payload of the event was the Chicago airport with the code ORD. This shows that your event fired correctly, allowing you to continue to use this event to develop additional features. In the next step, you will use this event to store the payload data in a Vuex store to keep track of a user’s favorite airports.

      In Vue.js Devtools, there is a specific pane set aside for monitoring Vuex actions, mutations, and stored state. By using this view, you can inspect your state for problems without having to iterate through source code changes. In this section, you will implement Vuex to make a list of favorite airports that a user can add to by clicking the airport card. You will then monitor these features with Vue.js Devtools.

      With your custom event created and executing, you will now take that data and call a Vuex action and mutation with it. If you are not familiar with Vuex, it is recommended to read more about it in our Vuex tutorial.

      In your text editor, open the src/store/index.js file and add the following code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          favorites: [],
        },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites({ state, commit }, payload) {
            const airports = state.favorites
            airports.push(payload)
            commit('UPDATE_FAVORITES', airports)
          }
        }
      })
      

      In this snippet, you are adding to the index.js file that was generated in Step 1. Specifically, you are adding a favorites state property (an array of airports.js objects), a mutation called UPDATE_FAVORITES, and an action called addToFavorites. The intent is for the user to click on a card, which will fire the action, which will fire the mutation, which will then update the store.

      In your App.vue component, add the following code to dispatch an action when your custom favorite-airport event is executed:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @favorite-airport="$store.dispatch('addToFavorites', $event)"/>
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      ...
      

      With your Vuex properties added, open your browser and open the Devtools. In the Components dropdown at the top of the pane, you will now find a Vuex option. Click to switch to the Vuex view.

      The Vuex dropdown in the Vue Devtools

      Similar to the Components section, the left panel is a visualization of your state tree, complete with namespaced modules and any root state that you might have. To the right is a full list of data in that module or section of your Vuex state.

      In the right panel, you will find an empty array of favorites. Now click on a few cards and see each airport object get pushed into that array:

      Animation showing airports being added to a favorites list in the Vuex store.

      Now that you know that the state is working, you can expand on this by adding an additional section to the App.vue file:

      favorite-airports/src/App.vue

      <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @favorite-airport="$store.dispatch('addToFavorites', $event)"/>
          </div>
      
        <div v-if="$store.state.favorites.length">
          <h1>Favorites <template v-if="$store.state.favorites.length">({{ $store.state.favorites.length }})</template></h1>
          <div v-for="airport in $store.state.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </div>
      ...
      

      Save the file, then click on a card in your browser. A new section will be displayed with cards of the airports that have been added.

      The Vue application rendered in a browser, with a list of favorite airports beneath the airport cards.

      Conclusion

      Vue.js Devtools is a free browser extension that you can add to any Chromium or Firefox broswer. You can also download it and as a standalone application that can be tied to your application. In this tutorial, you built an application from a dataset that rendered a number of card components, each with their own data. You then used Devtools to examine the properties of the components, change properties to test conditional style changes, test to see if events fired appropriately, and ensured that Vuex state management was set up correctly.

      To learn more about Vue.js Devtools, you can visit the official Vue DevTools documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Make Your Vue.js Application DRY with Slots, Mixins, and Composition API


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      DRY is a programming strategy that stands for “Don’t Repeat Yourself”. It encourages code re-usability through modular architecture, where code is re-used rather than repeated. This often results in code that is dynamic and scalable. In short, this principle guides programmers to avoid repeating code and hardcoding any values in your application.

      Vue.js includes several strategies to modularize and re-use repeated snippets of code. In this tutorial, you will try out these strategies by making a sample Vue.js application DRY. The tutorial will introduce ways to keep your template and script DRY within your component. You will use layout components that establish an HTML structure that you can place content into via slots. Then, you will use mixins, which are JavaScript files that contain data, methods, and computed properties that mix together with existing component options. Finally, you are going to use the new Composition API that was introduced in Vue 3. The Composition API is a different way to structure your components, and promotes the unification of component properties.

      Prerequisites

      Step 1 — Setting Up the Example Application

      To illustrate how you can create scalable and DRY Vue.js code, you’ll first set up an example application. This example application will be a main/detail application that shows a list of airport cards. When clicked, these cards will navigate you to another view with additional details on that airport.

      First, you’ll need to create a new Vue.js application. Do this by running the following command in your terminal:

      • vue create favorite-airports

      When prompted, select Manually select features. The following options to select are: Choose Vue version, Babel, and Router. Once selected, hit the RETURN key and continue filling out the prompts as follows:

      Output

      Vue CLI v4.5.15 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Router ? Choose a version of Vue.js that you want to start the project with 3.x ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

      Once the favorite-airports project is created, open your terminal window and cd (change directory) into the favroite-airports root folder. Once you’ve changed the directory, create a new directory to hold all of your local data for this project:

      Inside this folder, create a new Javascript file named src/data/airports.js and open it in your text editor of choice. To provide your app with sample data, add the following contents to the file:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY',
          destinations: {
            passenger: [ 'Toronto', 'Seattle/Tacoma', 'Austin', 'Charleston', 'Denver', 'Fort Lauderdale', 'Jacksonville', 'Las Vegas', 'Los Angeles', 'Baltimore', 'Chicago', 'Detroit', 'Dallas', 'Tampa' ],
            cargo: [ 'Anchorage', 'Baltimore', ' Chicago' , 'Indianapolis', 'Phoenix', 'San Francisco', 'Seattle', 'Louisville', 'Memphis' ]
          }
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
          destinations: {
            passenger: [ 'Dublin', 'Mexico City', 'Vancouver', 'Albuquerque', 'Atlanta', 'Frankfurt', 'Amsterdam', 'Salt Lake City', 'Tokyo', 'Honolulu' ],
            cargo: [ 'Spokane', 'Chicago', 'Dallas', ' Shanghai', 'Cincinnati', 'Luxenbourg', 'Anchorage', 'Juneau', 'Calgary', 'Ontario' ]
          }
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
          destinations: {
            passenger: [ 'Dublin', 'Paris', 'Punta Cana', 'Winnipeg', 'Tokyo', 'Denver', 'Tulsa', 'Washington DC', 'Orlando', 'Mexico City' ],
            cargo: [ 'Cincinnati', 'Omaha', 'Winnipeg', 'Chicago', 'St. Louis', 'Portland', 'Philadelphia', 'Milwaukee', 'Ontario' ]
          }
        }
      ]
      

      This is an array of objects consisting of a few airports in the United States. In the main view of this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties.

      Save data/airports.js and return to the terminal.

      When you’ve completed that step, create a single-file component (SFC) with the name AirportCard.vue. This file will live in the components directory of your project and will contain all the styles and logic for the airport cards. Open AirportCard.vue in your text editor and add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a “card”. The :first-child and :last-child are pseudo selectors that apply different styling to the first and last <p> tags in the HTML inside the <div> element with the class of airport. In addition to that, you may also notice that this component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component.

      Save and exit from the file.

      Before wrapping up this setup, replace the existing views/Home.vue component code with the following:

      favorite-airports/src/views/Home.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        data() {
          return {
            airports: allAirports
          }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      p,
      h3 {
        grid-column: span 3;
      }
      </style>
      

      This code contains a v-for loop that iterates through the airports.js data and renders a series of AirportCards.vue components with airport data passed in via the prop :airport. Save this code and return to the command line.

      With the project set up, run the local development server using the npm run serve command in your terminal:

      This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080 to see the following:

      A list of cards rendered in Vue using the v-for directive.

      Now that your example application is set up, in the next step you are going to create two different Vue.js components that you can later use as page layouts.

      Step 2 — Using slot to Create Layout Components

      Layout components are components that use slot elements to compose HTML templates that can be re-used with different content. These are great for when you have multiple templates that you want to re-use, such as a two-column or three-column layout.

      To create a layout component, you’ll first create a directory for them to live in. You could put them in the components folder, but since these components have a very specific job, your project will be more legible to other programmers if you differentiate them. Create a directory called layouts in the src directory:

      Next, create a file in your layouts directory named DefaultLayout.vue. The DefaultLayout.vue component will be a wrapper that contains the view’s content and centers it in the browser window. Open DefaultLayout.vue in your text editor and add the following:

      favorite-airports/src/layouts/DefaultLayout.vue

      <template>
        <div class="default-layout">
          <slot />
        </div>
      </template>
      
      <style scoped>
        .default-layout {
          max-width: 960px;
          margin: 0 auto;
        }
      </style>
      

      This component is a div with a class of default-layout. You can leverage this class to add some styles. The CSS styles you see in the component above restrict its width to a max of 960px, with side margins being automatic. This will center the div horizontally in the browser window. The slot element is a default slot. Anything that is placed between two <layout-default> tags will be injected to where this <slot /> is. You can try this out by refactoring the Home.vue that you modified in the previous step.

      Save your DefaultLayout.vue file. In your text editor, open src/views/Home.vue and import the DefaultLayout.vue component.

      favorite-airports/src/views/Home.vue

      ...
      <script>
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      import DefaultLayout from '@/layouts/DefaultLayout.vue'
      
      export default {
        components: {
          AirportCard,
          DefaultLayout
        },
        data() {
          return {
            airports: allAirports
          }
        }
      }
      </script>
      ...
      

      With the DefaultLayout.vue component imported, you can now replace the containing <div /> with <default-layout />.

      favorite-airports/src/views/Home.vue

      <template>
        <default-layout class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </default-layout>
      </template>
      ...
      

      You can now remove the max-width and margin properties in the .wrapper class that contain and center the div. Save this file, and open localhost:8080 in your browser window. Visually, nothing will change, but you now have a new layout component to contain any content in a centered div.

      Before you move on to the next step, you will create one more layout component. This is going to be a two-column layout. One column will be for supplemental information, and the other will be for the main content of the view. Create a new file at src/layouts/TwoColumnLayout.vue. Once created, open TwoColumnLayout.vue component in your text editor and add the following:

      favorite-airports/src/layouts/TwoColumnLayout.vue

      <template>
        <div class="two-column-layout">
          <aside>
            <slot name="sideBar" />
          </aside>
          <main>
            <slot name="content" />
          </main>
        </div>
      </template>
      
      <style>
        .two-column-layout {
          display: grid;
          grid-template-columns: 1fr 1fr 1fr;
          grid-column-gap: 1rem;
        }
      
        .two-column-layout aside,
        .two-column-layout main {
          border: 1px solid;
          border-radius: 5px;
        }
      
        .two-column-layout aside {
          grid-column: span 1;
        }
      
        .two-column-layout main {
          grid-column: span 2;
        }
      </style>
      

      In this component, you have two named slots, one for the sidebar and the other for the main content. On the containing <div>, you are using CSS to create a grid of three columns, with one spanning one column and the other spanning two.

      To use this layout, create a new view for the airport detail view at src/views/AirportDetail.vue, then add the following code to the new file:

      favorite-airports/src/views/AirportDetail.vue

      <template>
        <two-column-layout>
          <template v-slot:sideBar>
            <p>Sidebar</p>
          </template>
          <template v-slot:content>
            <p>Main Content</p>
          </template>
        </two-column-layout>
      </template>
      
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      
      export default {
        components: {
          TwoColumnLayout
        },
      }
      </script>
      

      This new view imports TwoColumnLayout.vue then uses v-slot to fill the named slots with the right content.

      Save this file. To make this view viewable, add a route in the Vue router file:

      favorite-airports/src/router/index.js

      import { createRouter, createWebHistory } from 'vue-router'
      import Home from '../views/Home.vue'
      import AirportDetail from '../views/AirportDetail'
      
      const routes = [
        {
          path: '/',
          name: 'Home',
          component: Home
        },
        {
          path: '/airport/:code',
          name: 'AirportDetail',
          component: AirportDetail
        },
        {
          path: '/about',
          name: 'About',
          // route level code-splitting
          // this generates a separate chunk (about.[hash].js) for this route
          // which is lazy-loaded when the route is visited.
          component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
        }
      ]
      ...
      

      Here you are registering a route that, when visited, will load the AirportDetail.vue component. The :code in the path is an argument that you can leverage later to grab a specific airport’s data.

      Save your file, then open your browser to localhost:8080/airport/cvg. You will find the following:

      The airport detail view, with a sidebar box and a main content box.

      In this step, you created layout components by leveraging slots. These layout components can help keep your app DRY by eliminating duplicate code when creating a structure of the webpage. In the next step, you are going try out mixins to share methods and properties between components.

      Step 3 — Using Mixins to Share Methods and Properties

      Mixins are a way to redistribute reusable component options to any number of components. When a mixin is imported, the mixin’s component options are “mixed in” to the current component. To illustrate this, this step will first run through an example of mixin syntax, and will then add mixins into your example app.

      Say you have two files with unique properties. The first has a data method and a computed property, like the following:

      sample-component

      <script>
        export default {
          data() {
            return {
              firstName: 'Dave',
              lastName: 'Berning'
            }
          },
          computed: {
            fullName() {
              return `${this.firstName} ${this.lastName}`
            }
          }
        }
      </script>
      

      The second is a file containing some component options that you want to re-use:

      someMixin

      export default {
        data() {
          return {
            counter: 0
          }
        },
        methods: {
          increment() {
            this.counter++
          }
        }
      }
      

      You can mix these two files together by importing the mixin (someMixin) into the component (sample-component). In this hypothetical component, you import it with the import keyword and assign it using the mixin property:

      sample-component

      <script>
      import someMixin from '@/mixins/someMixin'
      
      export default {
        data() {
          return {
            firstName: 'Dave',
            lastName: 'Berning'
          }
        },
        mixins: [ 'someMixin' ],
        computed: {
          fullName() {
            return `${this.firstName} ${this.lastName}`
          }
        }
      }
      </script>
      

      When imported, the hypothetical component has access to all methods, data, computed properties, and any other component options that it might contain.

      Next, you will create a mixin that contains a method and a data property. This function will combine the airport name and abbreviation and store it into a data property.

      In your terminal, create a new directory using the mkdir command:

      Make a file named src/mixins/airport.js and export an object containing the follow properties:

      src/mixins/airport.js

      export default {
        data() {
          return {
            airportWithCode: ''
          }
        },
        methods: {
          getAirportWithCode(airport) {
            this.airportWithCode = `${airport.name} - ${airport.abbreviation}`
          }
        }
      }
      

      This object will now have a data method and a method sets the data to an airport’s name and abbreviation. Save this file.

      With this created, import it into the Home.vue view. You are going to leverage this method and data property to display the string that is returned when the user clicks a card:

      src/views/Home.vue

      <template>
        <default-layout class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation" @click="getAirportWithCode(airport)">
            <airport-card :airport="airport" />
          </div>
          <p>test: {{ airportWithCode }}</p>
        </default-layout>
      </template>
      
      <script>
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      import DefaultLayout from '@/layouts/DefaultLayout.vue'
      import airportMixin from '@/mixins/airport.js'
      
        export default {
          components: {
            AirportCard,
            DefaultLayout
          },
          mixins: [ airportMixin ],
          data() {
            return {
              airports: allAirports
            }
          }
        }
      </script>
      ...
      

      Since you have access to the methods and data within that mixin, you can reference them like any other component option. You’ve done this in this code snippet to store an airportWithCode value when the user clicks a card, then render the string value in a paragraph element. Save the file.

      Next, re-use this same mixin in AirportDetail.vue. Open AirportDetail.vue in your text editor, then write a JavaScript filter to return the airport object if the abbreviation matches the :code argument that was defined in the router earlier:

      src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      
      export default {
        components: {
          TwoColumnLayout
        },
        data() {
          return {
            airport: ''
          }
        },
        methods: {
        getAirportByCode() {
          return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
        },
        mounted() {
          this.airport = this.getAirportByCode()
        }
      }
      </script>
      

      In this snippet, you are creating a new method named getAirportByCode that filters through the airport data and returns the airport object whose abbreviation matches the abbreviation in the route URL. On mounting, you are assigning the airport data property to that object that is returned.

      Save the file. Next, import the mixin you used earlier. You’ll leverage the same data and method properties you did before:

      src/views/AirportDetail.vue

      <template>
        <two-column-layout>
          <template v-slot:sideBar>
            <p>Sidebar</p>
          </template>
          <template v-slot:content>
            <p>Main Content</p>
            <p>{{ airportWithCode }}</p>
          </template>
        </two-column-layout>
      </template>
      
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import airportMixin from '@/mixins/airport.js'
      
      export default {
        components: {
          TwoColumnLayout
        },
        mixins: [ airportMixin ],
        data() { ... },
        methods: { ... },
        mounted() {
          this.airport = this.getAirportByCode()
          this.getAirportWithCode(this.airport)
        }
      }
      </script>
      

      Since you have determined the specific airport with getAirportByCode() and set it to this.airport, you can now use the getAirportWithCode method to set the value of airportWithCode from the mixin. You can then display the value by adding that variable to the template.

      Save this file, and open localhost:8080/airport/cvg in the browser window. You will find the string value of airportWithCode rendered in the main content section, as shown in the following image:

      Detail view of the CVG airport, with the string

      In this step, you used mixins to share methods and computed properties between components. Mixins can be a great way to organize components and leverage reusable code throughout your application. Next, you are going to learn about the new Composition API, why it was created, and how it can improve the composition of your next Vue application.

      Step 4 — Using the Composition API

      The mixins and layout components that you have tried out so far in this tutorial are usable in all major versions of Vue, including Vue 2 and earlier. These constitute the Options API. But in Vue 3 there is another API you can use to make your application DRY: the Composition API.

      The Composition API is a new way to set up components. Instead of having separate sections for data, computed, methods, and props, you have a single setup hook that everything lives in. Within this setup property, everything the component needs to operate before it is created will go in here. Additionally, everything needed to define options in the Options API needs to be imported. But this is not so for the Composition API.

      In this step, you are going to refactor one of your components from using the Options API to the new Composition API.

      In your text editor, open the AirportDetail.vue component. Right now, you have a mixin imported into this component. That mixin provides some functions and data properties. But in the Composition API, everything that your component needs to render will live inside the setup method; there’s no need to import this method.

      Remove your mixin import, and add the setup method under mounted:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      
      export default {
        components: { ... },
        methods: {
          getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
        },
        mounted() { ... },
        setup() {
      
        }
      }
      </script>
      

      With your setup method created, start refactoring this by adding the new onMounted lifecycle hook. The onMounted function accepts one argument, typically an anonymous function:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import { onMounted } from 'vue'
      
      export default {
        components: { ... },
        methods: {
          getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
        },
        mounted() { ... },
        setup() {
          onMounted(() => {
      
          })
        }
      }
      </script>
      

      When this component is mounted, you’ll store the current airport object into a reactive data property. To make a variable or constant reactive, you need to wrap the value in a ref function, which you will import from vue. The name of the constant will be the name of your reference in your setup and template sections of the component. Also, remove the getAirportByCode from methods and define it like a regular JavaScript function inside of setup:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import { onMounted, ref } from 'vue'
      
      export default {
        components: { ... },
        mounted() { ... },
        setup() {
          function getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
      
          onMounted(() => {
            const airport = ref(getAirportByCode())
          })
        }
      }
      </script>
      

      After you do that, you can go ahead and delete the old mounted and methods properties from the component file.

      Since you aren’t using a mixin now, you will define getAirportWithCode inside of your setup function and assign it to a variable airportWithCode so you can use it in your view:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import { onMounted, ref } from 'vue'
      
      export default {
        components: {
          TwoColumnLayout
        },
        setup() {
          const airportWithCode = ref('')
      
          function getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
      
          function getAirportWithCode(airport) {
            return `${airport.name} - ${airport.abbreviation}`
          }
      
            onMounted(() => {
              const airport = ref(getAirportByCode())
            })
          }
        }
      </script>
      

      One very important thing about reactive data properties with the Composition API is that ref returns an object. To access the values, you need to access its .value property:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import { onMounted, ref } from 'vue'
      
      export default {
        components: {
          TwoColumnLayout
        },
        setup() {
          const airportWithCode = ref('')
      
          function getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === this.$route.params.code.toUpperCase())[0]
          }
      
          function getAirportWithCode(airport) {
            return `${airport.name} - ${airport.abbreviation}`
          }
      
          onMounted(() => {
            const airport = ref(getAirportByCode())
            airportWithCode.value = getAirportWithCode(airport.value)
          })
        }
      }
      </script>
      

      There are now two things that you need to do before this can be completely converted to using the Composition API. The first thing you need to change is this.$route in the getAirportByCode function. In the Composition API, you cannot access the route or router with this.$route and this.$router, respectively.

      To access the route, import the useRoute from the vue-router package. It’s better to save this into a const that you can reference throughout the application:

      favorite-airports/src/views/AirportDetail.vue

      ...
      <script>
      import TwoColumnLayout from '@/layouts/TwoColumnLayout.vue'
      import allAirports from '@/data/airports.js'
      import { onMounted, ref } from 'vue'
      import { useRoute } from 'vue-router'
      
      export default {
        components: {
          TwoColumnLayout
        },
        setup() {
          const route = useRoute()
          const airportWithCode = ref('')
      
          function getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === route.params.code.toUpperCase())[0]
          }
      
          function getAirportWithCode(airport) {
            return `${airport.name} - ${airport.abbreviation}`
          }
      
          onMounted(() => {
            const airport = ref(getAirportByCode())
            airportWithCode.value = getAirportWithCode(airport.value)
          })
        }
      }
      </script>
      

      When that is done, return the object in your setup function. The properties returned in this object can be used in the template:

      favorite-airports/src/views/AirportDetail.vue

      ...
        setup() {
          const route = useRoute()
          const airportWithCode = ref('')
      
          function getAirportByCode() {
            return allAirports.filter(airport => airport.abbreviation === route.params.code.toUpperCase())[0]
          }
      
          function getAirportWithCode(airport) {
            return `${airport.name} - ${airport.abbreviation}`
          }
      
          onMounted(() => {
            const airport = ref(getAirportByCode())
            airportWithCode.value = getAirportWithCode(airport.value)
          })
      
          return { airportWithCode }
        }
      }
      </script>
      

      Save your code and reload localhost:8080/airport/cvg in your browser. After refactoring the code, there will be no change in what is rendered. However, you are now taking advantage of the Composition API.

      Conclusion

      In this tutorial, you tried out a few strategies to make your application DRY. Specifically, you re-used layout components in multiple views, then modularized properties and methods with mixins. Finally, you re-factored your app to use the new Composition API that was introduced in Vue 3. This API is a new way to set up components before they get created, making functional in more situations.

      If you’d like to learn more about Mixins or the Composition API, it’s highly encouraged to review the official Vue documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Manage State in a Vue.js Application with Vuex


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      Vuex is the first-party development state management library for Vue.js. It was created by Evan You and is currently maintained by the Vue.js Core Team. Like many other state management libraries, Vuex follows the principle that Redux has popularized over the past years: Data flows in one direction, with actions and mutations modifying data in a single source of truth called the store.

      A Vuex store is a collection of different methods and data. Some of these methods, such as actions, can fetch and process data before it is sent to a mutation. A mutation is a method that mutates or updates the store property with the value provided. Getters are methods that can modify or combine data to create a new state property. These getters are read-only and do not mutate data. These are similar to computed properties in a Vue.js component. The last component to Vuex is the state, or the dataset that acts as your single source of truth.

      In this tutorial, you will create an application that renders a list of cards with airport information in them. When clicked, these cards will execute the Vuex workflow to add the selected airport to a list of favorites. By running through this example, you will make actions and mutations to manage state and getters to retrieve computed data.

      Prerequisites

      Step 1 — Setting Up the Example Application

      In order to help visualize how state is managed with Vuex, set up a project with some data to display in the view. You will use this project and throughout the tutorial.

      Once the favorite-airports project is created as described in the Prerequisites section, create a directory to hold all of your local data for this project. Open your terminal and run the following commands in the project root directory (favorite-airports):

      • mkdir src/data
      • touch src/data/airports.js

      This will create the data directory and an empty airports.js file inside it.

      In your text editor of choice, open the newly created airports.js file and add in the following:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY'
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          abbreviation: 'MSY',
          city: 'New Orleans',
          state: 'LA',
        },
        {
          name: `Chicago O'hare International Airport`,
          abbreviation: 'ORD',
          city: 'Chicago',
          state: 'IL',
        },
        {
          name: `Miami International Airport`,
          abbreviation: 'MIA',
          city: 'Miami',
          state: 'FL',
        }
      ]
      

      This is an array of objects consisting of a few airports in the United States. In this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. When the user clicks on a card, you will execute a dispatch method, which will add that airport to your Vuex state as a favorite airport.

      Save data/airports.js and return to the terminal.

      When you’ve completed that step, create a single-file component (SFC) with the name AirportCard.vue. This file will live in the components directory of your project. This component will contain all the styles and logic for the airport card. In your terminal, create the .vue file using the touch command:

      • touch src/components/AirportCard.vue

      Open AirportCard.vue in your text editor and add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a “card”. The :first-child and :last-child are pseudo selectors that apply different styling to the first and last p tags in the HTML inside the div with the class of airport. In addition to that, you may also notice that this component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component.

      Save and exit from the file.

      Before wrapping up the setup, replace the existing App.vue component with the following code:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      p,
      h3 {
        grid-column: span 3;
      }
      </style>
      

      This code contains a v-for loop that iterates through the airports.js data and renders a series of AirportCards.vue components with airport data passed in via the prop :airport. Save this code and return to the command line.

      With the project set up, run the local development server using the npm run serve command in your terminal:

      This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080to see the following:

      A list of cards rendered in Vue using the v-for directive.

      Now that your example application is set up, in the next step you are going to install the Vuex library and create a store. This store is a collection of a number of different Vuex items including: state, mutations, actions, and getters. To illustrate this, you will be executing dispatch methods, which will add an airport to a favorites section of your app.

      Step 2 — Installing Vuex

      When working on web-based applications, you will often work with state. State is a collection of data at a given time. This state can be changed with user interactions via dispatch and commit methods. When the user modifies data, a dispatch event is executed, which passes data to a mutation and updates the state object.

      There are a few ways to approach updating state. Some developers skip actions and go straight to mutatations. However, for the sake of this tutorial, you will always execute an action that in turn calls a mutation. This way you can have multiple mutations inside of an action. The cardinal rule of Vuex is that mutations have one job and one job only: update the store. Actions can do a number of different things including combining data, fetching data, and running JavaScript logic.

      In addition to actions, there are also getters. A getter is a way to combine multiple state values into a single value. If you are familiar with computed properties in Vue.js, getters can be thought of as state-specific computed properties.

      With the Vuex terminology covered, start installing and integrating Vuex. Open your terminal and run the following command:

      • npm install vuex@next --save

      This command will install the version of Vuex that is the most compatible with Vue.js 3.x and saves it in your package.json file. Next, create a directory and an index file for your store. You will use the mkdir command to make a directory and touch to create a new file:

      • mkdir src/store
      • touch src/store/index.js

      Open your text editor and in your store/index.js file, initialize your Vuex store. To do this, you need to leverage the createStore function from Vuex:

      airport-favorites/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      You also export this, as you will later import it into your main.js file.

      At this point, you have your Vuex store set up, but the application is not yet aware of it or how to use it. To fully initialize the store, import it into your main.js file. In your text editor, open the src/main.js file.

      Immediately after createApp(App), chain the use method and pass into the store that you are importing, as shown in the following highlighted code:

      favorite-airports/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      import store from './store'
      
      createApp(App).use(store).mount('#app')
      

      Once you’ve chained the use method, save this file. The use method tells the Vue application which code to bundle together when the application is built. In this case, you are telling Vue to “use” or bundle the Vuex store.

      Before you move on to the next section, add a state value into your store and reference it in the App.vue file. Open your store/index.js file and add the following objects and values:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      state: {
          firstName: 'John',
          lastName: 'Doe'
        },
      mutations: {
      
      },
      actions: {
      
      },
      getters: {
      
      }
      })
      

      These properties reflect the type of data the store holds: state for state (global data), mutations (commits that mutate data), actions (dispatches that call mutations), and getters (store computed properties).

      Save store/index.js, then open your App.vue file in your text editor and add the following:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.state.firstName }} {{ $store.state.lastName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      The $store in this case is the global store that you initialized in the main.js file. If you were to log this.$store into the console, you would see the store object. From there, the code accesses the property you want to display via dot notation.

      Save App.vue then open your web browser. Above the airport cards, you will see the first and last name that you saved to your Vuex store. These are the default values of firstName and lastName, respectively.

      Data from the Vuex store displayed in the view.

      In this step, you installed Vuex and created a Vuex store. You added some default store data and displayed it in the view with the $store object using dot notion. In the next step, you will be updating your Vuex store via actions and mutations, and you will get combined data with getters.

      Step 3 — Creating Actions, Mutations, and Getters

      In Step 2, you installed Vuex manually and integrated it into your project. In this step you still have the first and last name rendered in your browser, but you will create a Vuex getter to render the data as one string. As mentioned before, you can think of Vuex getters as computed properties for your Vuex store.

      To create a getter, open your src/store/index.js file in your text editor of choice. Once open, create a property in the getters object with a function as its value. The name of the property is how you will access the getter later.

      Add the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function () {
      
          }
        }
      })
      

      In this case, you will use the function to combine the first and last names and store the resulting property as fullName. Inside the function, you will need to pass in the state object that is inside your Vuex store. From there, return a string with the first and last name interpolated:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      })
      

      You are using template literals here to put the firstName and lastName into one string.

      Save this file, then move back to App.vue. In this file, remove the first and last values and replace them with the getter:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      Once you make this change and save the file, your browser will hot reload. You will see the first and last name in your browser as before, but now you are leveraging the getter. If you change one of the names in your Vuex store, the getter will be updated automatically.

      Moving on from getters, there are actions. As mentioned in the last step, for the sake of this tutorial, you will always use an action rather than mutating the data directly.

      In this project, you will add an airport’s data to a “favorites” list when a user clicks on the card. You are going to first get the action and mutation created, then later assign it to a click event using the v-on directive.

      To create an action, open the src/store/index.js file in your text editor. In the actions section of the store, create a function. Like the getter, the function name will be how you reference the action later. Name this function addToFavorites:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: [] // will store favorites here
        },
        mutations: {
      
        },
        actions: {
          addToFavorites() {
      
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
      }
      })
      

      An action accepts two arguments: the context, or the Vue app itself, and the payload, or the data that you want to add to the store. The context has a commit method associated with it that you will use to call a mutation you will make later:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            context.commit('UPDATE_FAVORITES', payload)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      The commit method also accepts two arguments: the name of the mutation to call and the payload or the data that the mutation will replace the state with.

      In this code, you are naming the mutation UPDATE_FAVORITES. Mutation names should be agnostic and not named after a specific action. For example, a mutation like ADD_FAVORITE and REMOVE_FAVORITE implies a logic, like removing or adding a piece of data. This is not ideal, since mutations should have one job and one job only: update the state. To differentiate between adding and removing data, you could have two different actions that remove or add a favorite airport from the array, which then execute a single mutation called UPDATE_FAVORITES that updates the array with whatever was passed in. Minimizing the amount of mutations you have in your store will help make your Vuex store easier to manage as it grows larger in complexity and size.

      Next, add some logic to this action. When you add an airport as a “favorite”, you will add that payload (the airport data) to the existing array. To do that, you can use the push method in JavaScript:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      At this point, your action is set up to to add the payload to your favorites array then call a mutation with the mutated array as the new data. Next, you will define the UPDATE_FAVORITES mutation. Add the following code to set the favorites array as a new array:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
            firstName: 'John',
          lastName: 'Doe',
          favorites: []
          },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      Now that you have your action and your mutation, you can save this file.

      To execute this action, you can call a dispatch event when the user clicks on a card. You will do this with the v-on directive.

      Open the App.vue file in your text editor. On the <airport-card /> component, add the v-on directive shorthand syntax (@) with the event being click:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      The dispatch function accepts two arguments: the action name and the payload data that you are sending to the action.

      Save this file and open it in your browser. Now, when you click on an airport card, the action will call the mutation that updates the state and adds the airport to the favorites property.

      A favorite airport added to the favorite airports section after a Vuex mutation was executed.

      In this step, you expanded on the Vuex store that you created earlier. You created an action that copies an array and pushes a new item to that array. That action called a mutation that in turn updated the state. In addition to that, you learned about getters and how they can be leveraged to create new properties by combining or modifying read-only values in the Vuex store.

      In the final step, you will implement Vuex modules. Modules are a great way to break up your Vuex store into smaller Vuex stores. This is useful as your Vuex store growers larger in size and complexity.

      Step 4 — Composing Vuex Modules

      Modules are smaller Vuex stores that are combined into a single Vuex store. This is similar to how multiple Vue.js components are imported into a single .vue file, such as App.vue. In this step, you are going to separate this Vuex store into two separate modules. One module will be for a user state, and the other will be specific to airport state, actions, and mutations.

      In your terminal, cd into the store directory and use the touch command to create two separate files.

      • touch src/store/user.module.js
      • touch src/store/airports.module.js

      Inside of the user.module.js file, create an object that will be exported by default by adding the following code:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true
      }
      

      You are also adding the property namespaced with its value as true. The namespace property will make it so you can reference the module name while accessing a property with dot notation later.

      Inside of this object, you will add the state and getter information that is associated with a user:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true,
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      }
      

      The user module contains everything that you need for user information. Save and exit from the file.

      Go ahead and do the same thing in the airports.module.js file. Open the airports.module.js file in your text editor and add the following:

      favorite-airports/src/store/airports.module.js

      export default {
        state: {
          favorites: []
        },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
      }
      

      Now that you have put the airport-related mutations, actions, and state, you can save your airports.module.js.

      Next, import these two files into the main store/index.js file. In your text editor, open the store/index.js file and remove the state, mutations, actions, and getters properties. Your file will resemble the following snippet:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      To register modules, you will need to import them into this index.js file with the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      
      })
      

      From here, you will need to have a property called modules with an object as its value. The property names inside of this object will be the names of the Vuex modules. The value of the module name is the imported module itself:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      modules: {
        user: UserModule,
        airports: AirportsModule
      }
      })
      

      Once you save this file, your modules have now been registered and combined into your single Vuex store. Save store/index.js, then open the App.vue file and update it to reference the newly created modules:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters['user/fullName'] }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.airports.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.airports.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      Now you have a modular version of your Vuex setup.

      In this step, you segmented your existing Vuex store into smaller chunks called modules. These modules are a great way to group related store properties into a smaller Vuex store. You also updated your App.vue to reference the state and dispatch events in each module.

      Conclusion

      At a high level, state management is all about updating data. In this setup, the data in the state is global throughout your entire application and acts as a single source of truth, which can only be updated with explicit functions in the form of actions and mutations. In this tutorial, you ran through examples of state, mutations, actions, and getters and saw how each of these properties have their own purpose in the update cycle.

      To learn more about Vuex, actions, mutations, and modules, review the official Vuex documentation written by the Vue.js Core Team. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link