One place for hosting & domains

      Events

      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 Create User Interactions with Events in Vue


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

      Introduction

      In Vue.js development, a client’s web browser reads HTML and JavaScript and renders web pages based off of the instructions that the developer writes for it. But the web page or application not only needs to process data; it also needs to process user interactions. To do this, developers use events in JavaScript that execute code when the user interacts with HTML elements.

      An event can capture any user interaction with a user interface button or a physical keyboard or mouse. In JavaScript, you would create event listeners that wait for that event to occur and then execute a block of code. In Vue.js, you are not required to listen for an event; that is done automatically with the v-on: directive.

      In this tutorial, you will use events in Vue to create an application of airport codes. When the user selects an airport code, the app will add that airport to a “favorites” collection. By following along with this project, you will learn what events are, how to use Vue’s built-in events, and how to create your own custom events.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Setting Up the Project

      The first step in this tutorial will be to set up a demo project with some data to display in the view. This will include an array of JavaScript objects that contain airport data and a Vue component to iterate over and render the data.

      First, generate a project using Vue CLI:

      • vue create favorite-airports

      This will create a project named favorite-airports. This tutorial will use Vue 3, so when prompted, select the option Default (Vue 3) ([Vue 3] babel, eslint):

      Output

      Vue CLI v4.5.6 ? Please pick a preset: Default ([Vue 2] babel, eslint) ❯ Default (Vue 3) ([Vue 3] babel, eslint) Manually select features

      Once you have created the project, make a directory to hold all of your local data for this project. First, make the new project folder your working directory:

      Next, make a data directory in the src directory:

      In your text editor of choice, open a file called src/data/airports.js. Add the following data to the file:

      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 data is an array of objects consisting of a few airports in the United States. Next, 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, the app will emit an event up to the parent, which will add that airport to a collection of data that will represent your favorite airports.

      Save and close the airport.js file.

      To render the data, create a single-file component (SFC) with the name src/components/AirportCard.vue and open it in your text editor. This component will contain all of the styles and logic for the airport card.

      Add the following contents to the file:

      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;
      }
      
      .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>
      

      This component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component. The template section then renders this data. For more on single-file components, check out the How To Create Reusable Blocks of Code with Vue Single-File Components tutorial.

      You may notice that there is some CSS included in the 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. :first-child and :last-child are pseudo-selectors that apply different styling to the first and last p tags in the HTML inside of the div with the class of airport.

      Save the file and exit from your text editor.

      Next, modify the existing App.vue component to iterate through the airports.js data and render a series of AirportCards.vue components. Open src/App.vue in your text editor and replace the contents with the following highlighted 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;
      }
      </style>
      

      This imports the data and the SFC, then uses the v-for directive to iterate over the data, creating an airport card for each object in the airport.js array. It also adds additional CSS targeted to the wrapper class, which uses CSS grid to manage the layout of the cards.

      Save and exit the file. With the project now set up, run a local development server with the following command:

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

      A view of the airport data rendered on cards, with the airport abbreviation, full name, and location rendered in black, sans-serif font.

      Now that you have your sample project set up, you’ll next explore built-in events using the v-on directive. When this event is fired, an alert pop-up box will appear with the airport code of the airport associated with that event.

      Step 2 — Listening for Events With the v-on Directive

      As stated earlier, events are a way to execute functions when the user interacts with HTML elements in the DOM (Document Object Model). When writing vanilla JavaScript, to execute a function on an event, you may write something called an event listener. An event listener is a function that waits for that interaction to occur, then executes some code. With Vue, however, you can use the v-on directive for this purpose. A directive is a piece of re-useable code that a developer can use in order to manipulate the DOM. The v-on directive is provided by Vue.js out of the box.

      In this step, you will create a function in your application that runs when a user clicks on a card. Open the src/components/AirportCard.vue component in your text editor of choice.

      Create a function that alerts the user of the airport that they clicked on by adding the following highlighted code:

      favorite-airports/src/components/AirportCard.vue

      ...
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        },
        setup() {
          function selectAirport(airport) {
            alert(`You clicked on ${airport.abbreviation}. It's located in ${airport.city}, ${airport.state}.`)
          }
      
          return { selectAirport }
        }
      }
      </script>
      ...
      

      In Vue.js 3, reactive functions need to be defined and exported in the setup component method. This tells Vue that it can execute the selectAirport function in the <template>.

      With the function defined, you’ll now attach it to an event on an HTML element. As stated before, you can use the v-on directive and attach an event with the name of click; this is an event provided by Vue.js. In the AirportCard.vue component, add the v-on directive to the wrapper <div>:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" v-on:click="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      ...
      

      Once you have added this code, save and exit the file.

      Now, when you click on a card, an alert will pop-up with the message provided. If you click on CVG for example, you will find the following:

      Vue site with alert pop-up that reads "localhost:8080 says You clicked on CVG. It's located in Hebron, KY."

      The click event is not the only event that is provided to you out-of-the-box by Vue.js. In fact, you can use v-on any native JavaScript event, like:

      • keyup
      • mouseover
      • focus
      • mouseenter
      • change

      Next, you will change this v-on:click listener to mouseover to illustrate how Vue.js listens for events. mouseover is an event that fires whenever a mouse cursor moves over an HTML element.

      Open up src/components/AirportCard.vue again and update your file with the following highlighted code:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @mouseover="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      As shown here, Vue also has shorthand syntax for v-on: events. To use the shorthand syntax, you replaced v-on with @. Save and exit the file.

      Now when you visit localhost:8080 and hover over a card, that function will execute and display a native alert.

      This functionality is good for testing purposes, but may be undesired since it displays the alert every time a user hovers over it. A better experience might be to only display it the first time a user hovers over that card. In vanilla JavaScript, you may track the amount of times a user hovers over a card, then prevent further executions. Vue.js has event modifiers that you can leverage to accomplish the same thing with less code.

      In the next section, you are going to explore event modifiers and use them for a better user experience.

      Step 3 — Using Event and Key Modifiers

      In the previous section, you executed a function on the click and mouseover events. You also learned about the Vue.js shorthand for v-on events. Now you will expand on this further by attaching a modifier to this mouseover event so your function executes only once.

      Vue.js provides a number of event modifiers for you. Some of these include:

      • .stop: stops event propagation
      • .prevent: prevents the HTML element’s default behavior
      • .capture: handles an event targeting an inner element before the selected element
      • .self: only triggers the handler if event.target is the element itself
      • .once: only executes the function once
      • .passive: enables the element’s default behavior to happen immediately instead of waiting for the event, which can be used for optimizing performance for scroll on mobile devices

      In this case, you’ll use the .once modifier. In your text editor, open the AirportCard.vue component and add the modifier to the existing mouseover event:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @mouseover.once="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      Save the file. Visit your application in the browser and you’ll find that the event only fires once on the first mouseover event.

      Next, you’ll continue exploring modifiers by using key modifiers. These key modifiers are associated with keystroke events, such as keyup. For this next part, imagine that you want to make this clicking action a little more explicit. One way you can do that is by adding a key modifier to the @click event on the .airport <div> in your template.

      To do that, change the @mouseover to @click and add the .shift modifier:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @click.shift="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      Save the changes and open the application in your browser. If you click on a card without holding the SHIFT key, the alert does nothing. Now, try holding down the SHIFT key when clicking on a card. Your function will now execute, and you will receive an alert.

      In this section, you learned about Vue’s built-in events and the modifiers associated with those events. You can get a lot done with these built-in events, but there will be times when you’ll need to have a custom event. In the next section, you’re going to use custom events to emit an action up to a parent so that it will execute a function.

      Step 4 — Creating Custom Events

      When developing applications in Vue.js, there will be times when you need to pass data up to a parent component via a custom event. Props are read-only data that are passed down to a child from the parent, but a custom action via an $emit is the opposite of that. To create the most reusable components, it’s best to think of these as functions. You pass data down through props (arguments), and emit values back up to the parent (a return value).

      To emit an event from the child component to the parent, you use the $emit function. Before implementing this, this tutorial will guide you through an example to demonstrate how this works.

      The $emit function accepts two arguments: the action name (a string), and the value to pass up to the parent. In the following example, when the user clicks on the button, you are sending the value CVG to the parent component under the action favoriteAirport:

      ChildComponent.vue

      <template>
        <button @click="$emit('favoriteAirport', 'CVG')">A button</button>
      </template>
      

      In the parent component, you would use the v-on directive and listen for the favoriteAirport event. When this custom event is fired, the code will do something with the value:

      ParentComponent.vue

      <template>
        <child-component @favoriteAirport="favoriteAirport = $event" />
      </template>
      
      <script>
      import { ref } from 'vue'
      export default {
        setup() {
          const favoriteAirport = ref('')
      
          return { favoriteAirport }
        }
      }
      </script>
      

      The value of the event will be $event. In this case, $event is actually CVG, which you then store in a reactive data property called favoriteAirport.

      Now that you know what a custom event looks like, you will put it into practice by implementing this custom event into your application.

      Open the AirportCards.vue component in your text editor. In the @click event, remove the reference to the function and replace it with $emit("favoriteAirport", airport). Remember, the first arugment is the name of the event and the second is the value that you are emitting:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @click="$emit('favoriteAirport', airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      ...
      

      Save the file. Now, when the user clicks on the airport card, a custom event will fire and pass up that airport object.

      Next, open src/App.vue to add some HTML to the template. You will show the favorite airports list after the six cards that are already present:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
          <h1 v-if="favoriteAirports.length">Favorite Airports</h1>
          <div v-for="airport in favoriteAirports" :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)
          const favoriteAirports = ref([])
      
          return { airports, favoriteAirports }
        }
      }
      </script>
      ...
      

      In this code snippet, you are creating a reactive data property called favoriteAirports, which is an empty array. In the <template>, you iterate through the empty array to render the <airport-card /> components, much like you did in an earlier step.

      Now you need to add the v-on event for your custom event:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @favoriteAirport="favoriteAirports.push($event)" />
          </div>
          <h1 v-if="favoriteAirports.length">Favorite Airports</h1>
          <div v-for="airport in favoriteAirports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
         </div>
        </div>
      </template>
      ...
      

      In the @favoriteAiport custom event, you used the JavaScript push() method to add the airport from the child ($event) to the favoriteAirports reactive data property.

      Open you browser and navigate to your project at localhost:8080. When you click on one of the airport cards, that card will appear under Favorite Airports.

      Vue airport app with a list of favorite airports that includes the CVG airport card.

      In this section, you learned about custom events, what they are, and how to use them. A custom event is a way to pass data up to a parent component through the $emit function provided by Vue. Once that data has been emitted, you can further manipulate it in the parent component, like adding it to an array.

      Conclusion

      In this tutorial, you learned how Vue.js listens for a number of built-in events, such as click and mouseover. In addition to that, you tried out event and key modifiers, small pieces of code that you appended to your event to provide additional functionality. With this, you set up your app to execute the function once with the .once modifier and to only fire when holding down the SHIFT key using the .shift modifier.

      Vue provides an efficient way to listen for events that lets you focus on manipulating data over manually setting up event listeners. In addition to that, Vue allows you to think of components as functions: They accept data props and can return a value with $emit.

      To learn more about Vue components, it is recommended to read through the Vue documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Handle DOM and Window Events with React


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In web development, events represent actions that happen in the web browser. By responding to events with event handlers, you can create dynamic JavaScript applications that respond to any user action, including clicking with a mouse, scrolling along a webpage, touching a touch screen, and more.

      In React apps, you can use event handlers to update state data, trigger prop changes, or prevent default browser actions. To do this, React uses a SyntheticEvent wrapper instead of the native Event interface. SyntheticEvent closely emulates the standard browser event, but provides more consistent behavior for different web browsers. React also gives you tools to safely add and remove a Window event listener when a component mounts and unmounts from the Document Object Model (DOM), giving you control over Window events while preventing memory leaks from improperly removed listeners.

      In this tutorial, you’ll learn how to handle events in React. You’ll build several sample components that handle user events, including a self-validating input component and an informative tooltip for the input form. Throughout the tutorial, you’ll learn how to add event handlers to components, pull information from the SyntheticEvent, and add and remove Window event listeners. By the end of this tutorial, you’ll be able to work with a variety of event handlers and apply the catalog of events supported by React.

      Prerequisites

      In this step, you’ll create a validating component using an <input> HTML element and the onChange event handler. This component will accept input and validate it, or make sure that the content adheres to a specific text pattern. You’ll use the SyntheticEvent wrapper to pass event data into the callback function and update the component using the data from the <input>. You will also call functions from the SyntheticEvent, such as preventDefault to prevent standard browser actions.

      In React, you don’t need to select elements before adding event listeners. Instead, you add event handlers directly to your JSX using props. There are a large number of supported events in React, including common events such as onClick or onChange and less common events such as onWheel.

      Unlike native DOM onevent handlers, React passes a special wrapper called SyntheticEvent to the event handler rather than the native browser Event. The abstraction helps reduce cross-browser inconsistencies and gives your components a standard interface for working with events. The API for SyntheticEvent is similar to the native Event, so most tasks are accomplished in the same manner.

      To demonstrate this, you will start by making your validating input. First, you will create a component called FileNamer. This will be a <form> element with an input for naming a file. As you fill in the input, you’ll see the information update a preview box above the component. The component will also include a submit button to run the validation, but for this example the form will not actually submit anything.

      First, create the directory:

      • mkdir src/components/FileNamer

      Then open FileNamer.js in your text editor:

      • nano src/components/FileNamer/FileNamer.js

      Inside FileNamer.js, create a wrapper <div>, then add another <div> with a class name of preview and a <form> element inside the wrapper by writing the following lines of code:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React from 'react';
      
      export default function FileNamer() {
        return(
          <div className="wrapper">
            <div className="preview">
            </div>
            <form>
            </form>
          </div>
        )
      }
      

      Next, add an input element for the name to display in the preview box and a Save button. Add the following highlighted lines:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React from 'react';
      
      export default function FileNamer() {
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview:</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input name="name" />
              </label>
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      In the preview <div>, you added an <h2> element with the text Preview. This will be your preview box. Inside your form, you added an <input> surrounded by a <label> element with Name: as its text. Then you added a button called Save directly before the closing <form> tag.

      Save and close the file.

      Next, open App.js:

      • nano src/components/App/App.js

      Import FileNamer, then render inside the App function by adding the following highlighted lines:

      events-tutorial/src/components/App/App.js

      import React from 'react';
      import FileNamer from '../FileNamer/FileNamer';
      
      function App() {
          return <FileNamer />
      }
      
      export default App;
      

      Save and close the file. When you do the browser will refresh and you’ll see your component.

      Name element

      Next, add some light styling to help define the sections and to add some padding and margins to the elements.

      Open FileNamer.css in your text editor:

      • nano src/components/FileNamer/FileNamer.css

      Give the .preview class a gray border and padding, then give the .wrapper class a small amount of padding. Display the items in a column using flex and flex-direction, and make all the text align left. Finally, remove the default button styles by removing the border and adding a black border:

      events-tutorial/src/components/FileNamer/FileNamer.css

      .preview {
          border: 1px darkgray solid;
          padding: 10px;
      }
      
      .wrapper {
          display: flex;
          flex-direction: column;
          padding: 20px;
          text-align: left;
      }
      
      .wrapper button {
          background: none;
          border: 1px black solid;
          margin-top: 10px;
      }
      

      Save and close the file. Then open FileNamer.js:

      • nano src/components/FileNamer/FileNamer.js

      Import the styles to apply them to your component:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview:</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input name="name" />
              </label>
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      Save the file. When you do, the browser will refresh and you’ll find the component has the new styles.

      Styled component

      Now that you have a basic component, you can add event handlers to the <input> element. But first, you’ll need a place to store the data in the input field. Add the useState Hook to hold the input:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input name="name" />
              </label>
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      In this code, you destructured useState into a variable name to hold the input and a function called setName to update the data. Then you displayed the name in the preview section followed by the .js extension, as if the user were naming a file.

      Now that you can store the input data, you can add an event handler to the <input> component. There are often several different event handlers you can use for a given task. In this case, your app needs to capture the data the user types into the element. The most common handler for this situation is onChange, which fires every time the component changes. However, you could also use keyboard events, such as onKeyDown, onKeyPress, and onKeyUp. The difference primarily has to do with when the event fires and the information passed to the SyntheticEvent object. For example, onBlur, an event for when an element becomes unfocused, fires before onClick. If you want to handle user information before another event fires, you can pick an earlier event.

      Your choice of event is also determined by the type of data you want to pass to the SyntheticEvent. The onKeyPress event, for example, will include the charCode of the key that the user pressed, while onChange will not include the specific character code, but will include the full input. This is important if you want to perform different actions depending on which key the user pressed.

      For this tutorial, use onChange to capture the entire input value and not just the most recent key. This will save you the effort of storing and concatenating the value on every change.

      Create a function that takes the event as an argument and pass it to the <input> element using the onChange prop:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input name="name" onChange={event => {}}/>
              </label>
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      As mentioned earlier, the event here is not the native browser event. It’s the SyntheticEvent provided by React, which is often treated the same. In the rare case you need the native event, you can use the nativeEvent attribute on the SyntheticEvent.

      Now that you have the event, pull out the current value from the target.value property of the event. Pass the value to setName to update the preview:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                 autoComplete="off"
                 name="name"
                 onChange={event => setName(event.target.value) }
               />
              </label>
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      In addition, you set the attribute autoComplete to "off" to turn off browser suggestions.

      Save the file. When you do, the page will reload, and when you type in the <input> you’ll see an update in the preview.

      Typing into the input element

      Note: You could also access the name of the input using event.target.name. This would be useful if you were using the same event handler across multiple inputs, since the name would automatically match the name attribute of the component.

      At this point, you have a working event handler. You are taking the user information, saving it to state, and updating another component with the data. But in addition to pulling information from an event, there are situations where you’ll need to halt an event, such as if you wanted to prevent a form submission or prevent a keypress action.

      To stop an event, call the preventDefault action on the event. This will stop the browser from performing the default behavior.

      In the case of the FileNamer component, there are certain characters that could break the process of choosing a file that your app should forbid. For example, you wouldn’t want a user to add a * to a filename since it conflicts with the wildcard character, which could be interpreted to refer to a different set of files. Before a user can submit the form, you’ll want to check to make sure there are no invalid characters. If there is an invalid character, you’ll stop the browser from submitting the form and display a message for the user.

      First, create a Hook that will generate an alert boolean and a setAlert function. Then add a <div> to display the message if alert is true:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
      
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autoComplete="off"
                  name="name"
                  onChange={event => setName(event.target.value) }
                />
              </label>
              {alert && <div> Forbidden Character: *</div>}
              <div>
                <button>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      In this code, you used the && operator to only show the new <div> if alert is set equal to true first. The message in the <div> will tell the user that the * character is not allowed in the input.

      Next, create a function called validate. Use the regular expression .test method to find out if the string contains a *. If it does, you will prevent the form submission:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
        const validate = event => {
          if(/*/.test(name)) {
            event.preventDefault();
            setAlert(true);
            return;
          }
            setAlert(false);
       };
      
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autoComplete="off"
                  name="name"
                  onChange={event => setName(event.target.value) }
                />
              </label>
              {alert && <div> Forbidden Character: *</div>}
              <div>
                <button onClick={validate}>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      When the validate function is called and the test returns true, it will use event.preventDefault then call setAlert(true). Otherwise, it will call setAlert(false). In the last part of the code, you added the event handler to the <button> element with onClick.

      Save the file. As before, you could have also used onMouseDown, but onClick is more common and thus allows you to avoid any unexpected side effects. This form doesn’t have any submit actions, but by preventing the default action, you prevent the page from reloading:

      Prevent Default and trigger a warning

      Now you have a form that uses two event handlers: onChange and onClick. You are using the event handlers to connect user actions to the component and the application, making it interactive. In doing so, you learned to add events to DOM elements, and how there are several events that fire on the same action, but that provide different information in the SyntheticEvent. You also learned how to extract information from the SyntheticEvent, update other components by saving that data to state, and halt an event using preventDefault.

      In the next step, you’ll add multiple events to a single DOM element to handle a variety of user actions.

      Step 2 — Adding Multiple Event Handlers to the Same Element

      There are situations when a single component will fire multiple events, and you’ll need to be able to connect to the different events on a single component. For example, in this step you’ll use the onFocus and onBlur event handlers to give the user just-in-time information about the component. By the end of this step, you’ll know more about the different supported events in React and how to add them to your components.

      The validate function is helpful for preventing your form from submitting bad data, but it’s not very helpful for user experience: The user only receives information about the valid characters after they’ve filled out the entire form. If there were multiple fields, it wouldn’t give the user any feedback until the last step. To make this component more user friendly, display the allowed and disallowed characters when the user enters the field by adding an onFocus event handler.

      First, update the alert <div> to include information about what characters are allowed. Tell the user alphanumeric characters are allowed and the * is not allowed:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
      ...
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autocomplete="off"
                  name="name"
                  onChange={event => setName(event.target.value) }
                />
              </label>
              {alert &&
               <div>
                 <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
                 <br />
                 <span role="img" aria-label="not allowed">⛔️</span> *
               </div>
             }
              <div>
                <button onClick={validate}>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      In this code, you used Accessible Rich Internet Applications (ARIA) standards to make the component more accessible to screen readers.

      Next, add another event handler to the <input> element. You will alert the user about the allowed and disallowed characters when they activate the component by either clicking or tabbing into the input. Add in the following highlighted line:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
      ...
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autocomplete="off"
                  name="name"
                  onChange={event => setName(event.target.value) }
                  onFocus={() => setAlert(true)}
                />
              </label>
              {alert &&
                <div>
                  <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
                  <br />
                  <span role="img" aria-label="not allowed">⛔️</span> *
                </div>
              }
              <div>
                <button onClick={validate}>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      You added the onFocus event handler to the <input> element. This event triggers when the user selects the field. After adding the event handler, you passed an anonymous function to onFocus that will call setAlert(true) and display the data. In this case, you don’t need any information from the SyntheticEvent; you only need to trigger an event when the user acts. React is still sending the SyntheticEvent to the function, but in the current situation you don’t need to use the information in it.

      Note: You could trigger the data display with onClick or even onMouseDown, but that wouldn’t be accessible for users that use the keyboard to tab into the form fields. In this case, the onFocus event will handle both cases.

      Save the file. When you do, the browser will refresh and the information will remain hidden until the user clicks on the input.

      Trigger the event when clicking on the input

      The user information now appears when the field is focused, but now the data is present for the duration of the component. There’s no way to make it go away. Fortunately, there’s another event called onBlur that fires when the user leaves an input. Add the onBlur event handler with an anonymous function that will set the alert to false. Like onFocus, this will work both when a user clicks away or when a user tabs away:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
      ...
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autocomplete="off"
                  name="name"
                  onBlur={() => setAlert(false)}
                  onChange={event => setName(event.target.value) }
                  onFocus={() => setAlert(true)}
                />
              </label>
              {alert &&
                <div>
                  <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
                  <br />
                  <span role="img" aria-label="not allowed">⛔️</span> *
                </div>
              }
              <div>
                <button onClick={validate}>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      Save the file. When you do, the browser will refresh and the information will display when the user clicks on the element and disappear when the user clicks away:

      Show information on focus and hide on blur

      You can add as many event handlers as you need to an element. If you have an idea of an event you need, but aren’t sure of the name, scroll through the supported events and you may find what you need.

      In this step you added multiple event handlers to a single DOM element. You learned how different event handlers can handle a broad range of events—such as both click and tab—or a narrow range of events.

      In the next step, you’ll add global event listeners to the Window object to capture events that occur outside the immediate component.

      Step 3 — Adding Window Events

      In this step, you’ll put the user information in a pop-up component that will activate when the user focuses an input and will close when the user clicks anywhere else on the page. To achieve this effect, you’ll add a global event listener to the Window object using the useEffect Hook. You’ll also remove the event listener when the component unmounts to prevent memory leaks, when your app take up more memory than it needs to.

      By the end of this step, you’ll be able to safely add and remove event listeners on individual components. You’ll also learn how to use the useEffect Hook to perform actions when a component mounts and unmounts.

      In most cases, you’ll add event handlers directly to DOM elements in your JSX. This keeps your code focused and prevents confusing situations where a component is controlling another component’s behavior through the Window object. But there are times in which you’ll need to add global event listeners. For example, you may want a scroll listener to load new content, or you may want to capture click events outside of a component.

      In this tutorial, you only want to show the user the information about the input if they specifically ask for it. After you display the information, you’ll want to hide it whenever the user clicks the page outside the component.

      To start, move the alert display into a new <div> with a className of information-wrapper. Then add a new button with a className of information and an onClick event that will call setAlert(true):

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
      ...
        return(
          <div className="wrapper">
            <div className="preview">
              <h2>Preview: {name}.js</h2>
            </div>
            <form>
              <label>
                <p>Name:</p>
                <input
                  autocomplete="off"
                  name="name"
                  onChange={event => setName(event.target.value) }
                />
              </label>
              <div className="information-wrapper">
                <button
                  className="information"
                  onClick={() => setAlert(true)}
                  type="button"
                >
                  more information
                </button>
               {alert &&
                 <div className="popup">
                   <span role="img" aria-label="allowed">✅</span> Alphanumeric Characters
                   <br />
                   <span role="img" aria-label="not allowed">⛔️</span> *
                 </div>
               }
              </div>
              <div>
                <button onClick={validate}>Save</button>
              </div>
            </form>
          </div>
        )
      }
      

      You also removed the onFocus and onBlur handlers from the <input> element to remove the behavior from the last step.

      Save and close the file. Then open FileNamer.css:

      • nano src/components/FileNamer/FileNamer.css

      Add some styling to absolutely position the popup information above the button. Then change the <button> with a class of information to be blue with no border.

      events-tutorial/src/components/FileNamer/FileNamer.css

      
      .information {
         font-size: .75em;
         color: blue;
         cursor: pointer;
      }
      
      .wrapper button.information {
          border: none;
      }
      
      .information-wrapper {
         position: relative;
      }
      
      .popup {
          position: absolute;
          background: white;
          border: 1px darkgray solid;
          padding: 10px;
          top: -70px;
          left: 0;
      }
      
      .preview {
          border: 1px darkgray solid;
          padding: 10px;
      }
      
      .wrapper {
          display: flex;
          flex-direction: column;
          padding: 20px;
          text-align: left;
      }
      
      .wrapper button {
          background: none;
          border: 1px black solid;
          margin-top: 10px;
      }
      

      Save and close the file. When you do, the browser will reload, and when you click on more information, the information about the component will appear:

      Trigger information pop-up

      Now you can trigger the pop-up, but there’s no way to clear it. To fix that problem, add a global event listener that calls setAlert(false) on any click outside of the pop-up.

      The event listener would look something like this:

      window.addEventListener('click', () => setAlert(false))
      

      However, you have to be mindful about when you set the event listener in your code. You can’t, for example, add an event listener at the top of your component code, because then every time something changed, the component would re-render and add a new event listener. Since your component will likely re-render many times, that would create a lot of unused event listeners that take up memory.

      To solve this, React has a special Hook called useEffect that will run only when specific properties change. The basic structure is this:

      useEffect(() => {
       // run code when anything in the array changes
      }, [someProp, someOtherProp])
      

      In the simplified example, React will run the code in the anonymous function whenever someProp or someOtherProp changes. The items in the array are called dependencies. This Hook listens for changes to the dependencies and then runs the function after the change.

      Now you have the tools to add and remove a global event listener safely by using useEffect to add the event listener whenever alert is true and remove it whenever alert is false.

      There is one more step. When the component unmounts, it will run any function that you return from inside of your useEffect Hook. Because of this, you’ll also need to return a function that removes the event listener when the component unmounts.

      The basic structure would be like this:

      useEffect(() => {
       // run code when anything in the array changes
        return () => {} // run code when the component unmounts
      }, [someProp, someOtherProp])
      

      Now that you know the shape of your useEffect Hook, use it in your application. Open up FileNamer.js:

      • nano src/components/FileNamer/FileNamer.js

      Inside, import useEffect, then add an empty anonymous function with a dependency of alert and setAlert in the array after the function:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useEffect, useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
      
        useEffect(() => {
        }, [alert, setAlert]);
      ...
      

      In this code, you added both alert and setAlert. To be complete, React recommends you add all external dependencies to the useEffect function. Since you will be calling the setAlert function, it can be considered a dependency. setAlert will not change after the first render, but it’s a good practice to include anything that could be considered a dependency.

      Next, inside the anonymous function, create a new function called handleWindowClick that calls setAlert(false):

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useEffect, useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
      
        useEffect(() => {
          const handleWindowClick = () => setAlert(false)
        }, [alert, setAlert]);
        ...
      }
      

      Then add a conditional that will call window.addEventListener('click', handleWindowClick) when alert is true and will call window.removeEventListener('click', handleWindowClick) when alert is false. This will add the event listener every time you trigger the pop-up and remove it everytime the pop-up is closed:

      events-tutorial/src/components/FileNamer/FileNamer.js

      import React, { useEffect, useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
      
        useEffect(() => {
          const handleWindowClick = () => setAlert(false)
          if(alert) {
            window.addEventListener('click', handleWindowClick);
          } else {
            window.removeEventListener('click', handleWindowClick);
          }
        }, [alert, setAlert]);
        ...
      }
      

      Finally, return a function that will remove the event listener. Once again, this will run when the component unmounts. There may not be a live event listener, but it’s still worth cleaning up in situations where the listener still exists:

      events-tutorial/src/components/FileNamer/FileNamer.js

      
      import React, { useEffect, useState } from 'react';
      import './FileNamer.css';
      
      export default function FileNamer() {
        const [name, setName] = useState('');
        const [alert, setAlert] = useState(false);
      
        useEffect(() => {
          const handleWindowClick = () => setAlert(false)
          if(alert) {
            window.addEventListener('click', handleWindowClick);
          } else {
            window.removeEventListener('click', handleWindowClick)
          }
          return () => window.removeEventListener('click', handleWindowClick);
        }, [alert, setAlert]);
        ...
      }
      

      Save the file. When you do, the browser will refresh. If you click on the more information button, the message will appear. If you look at the global event listeners in the developer tools, you’ll see there is a click listener:

      Click event listener

      Click anywhere outside the component. The message will disappear and you’ll no longer see the global click event listener.

      No click event listener

      Your useEffect Hook successfully added and removed a global event listener based on a user interaction. It wasn’t tied to a specific DOM element, but was instead triggered by a change in the component state.

      Note: From an accessibility standpoint, this component is not complete. If a user is not able to use the mouse, they will be stuck with an open pop-up because they would never be able to click outside the component. The solution would be to add another event listener for keydown that would also remove the message. The code would be nearly identical except the method would be keydown instead of click.

      In this step you added global event listeners inside a component. You also learned how to use the useEffect Hook to properly add and remove the event listener as the state changes and how to clean up event listeners when the component unmounts.

      Conclusion

      Event handlers give you the opportunity to align your components with user actions. These will give your applications a rich experience and will increase the interactive possibilities of your app. They will also give you the ability to capture and respond to user actions.

      React’s event handlers let you keep your event callbacks integrated with the HTML so that you can share functionality and design across an application. In most cases, you should focus on adding event handlers directly to DOM elements, but in situations where you need to capture events outside of the component, you can add event listeners and clean them up when they are no longer in use to prevent memory leaks and create performative applications.

      If you would like to look at more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page. To learn more about events in JavaScript, read our Understanding Events in JavaScript and Using Event Emitters in Node.js tutorials.



      Source link