📜 ⬆️ ⬇️

How to search for users on GitHub using Vue

I think everyone already knows how to write a search for GitHub users on React , Svelte , Angular , or even without them . So how can you do without Vue? It's time to fill this gap.


image


So today we will create that application using Vue, write tests for it on Cypress and touch on Vue CLI 3 a bit.


There are gifs in the post


Training


First, install the latest version of the Vue CLI:


npm i -g @vue/cli 

And run the creation of the project:


 vue create vue-github-search 

Follow the steps of the generator. For our project, I chose Manual mode and the following configuration:


image


Additional modules


We will use Stylus as styles, so we will need a stylus and a stylus-loader. We also need Axios for network requests and Lodash , from which we will take the debounce function.


Go to the project folder and install the necessary packages:


 cd vue-github-search npm i stylus stylus-loader axios lodash 

Check


We start the project and make sure that everything works:


  npm run serve 

All changes in the code will be instantly applied in the browser without reloading the page.


Store


Let's start with what we write vuex store, where there will be all the data applications. All we need to do is store anything: a search query, user data, and a boot process flag.
Open the store.js and describe the initial state of the application and the necessary mutations:


 ... const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER'; export default new Vuex.Store({ state: { searchQuery: '', loading: false, user: null }, mutations: { [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery, [SET_LOADING]: (state, loading) => state.loading = loading, [SET_USER]: (state, user) => state.user = user, [RESET_USER]: state => state.user = null } }); 

Add action actions to load data from the GitHub API and to change the search query (we need it for the search string). As a result, our store will look like this:


store.js


 import Vue from 'vue'; import Vuex from 'vuex'; import axios from 'axios'; Vue.use(Vuex); const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER'; export default new Vuex.Store({ state: { searchQuery: '', loading: false, user: null }, mutations: { [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery, [SET_LOADING]: (state, loading) => state.loading = loading, [SET_USER]: (state, user) => state.user = user, [RESET_USER]: state => state.user = null }, actions: { setSearchQuery({commit}, searchQuery) { commit(SET_SEARCH_QUERY, searchQuery); }, async search({commit, state}) { commit(SET_LOADING, true); try { const {data} = await axios.get(`https://api.github.com/users/${state.searchQuery}`); commit(SET_USER, data); } catch (e) { commit(RESET_USER); } commit(SET_LOADING, false); } } }); 

Search line


Create a new Search.vue component in the components folder. Add a computed property to associate the component with the store. At changes of search query we will cause search with debounce.


Search.vue


 <template> <input v-model="query" @input="debouncedSearch" placeholder="Enter username" /> </template> <script> import {mapActions, mapState} from 'vuex'; import debounce from 'lodash/debounce'; export default { name: 'search', computed: { ...mapState(['searchQuery']), query: { get() { return this.searchQuery; }, set(val) { return this.setSearchQuery(val); } } }, methods: { ...mapActions(['setSearchQuery', 'search']), debouncedSearch: debounce(function () { this.search(); }, 500) } }; </script> <style lang="stylus" scoped> input width 100% font-size 16px text-align center </style> 

Now we connect our search string to the main component of App.vue and in the App.vue delete the extra lines created by the generator.


App.vue


 <template> <div id="app"> <Search /> </div> </template> <script> import Search from './components/Search'; export default { name: 'app', components: { Search } }; </script> <style lang="stylus"> #app font-family 'Avenir', Helvetica, Arial, sans-serif font-smoothing antialiased margin 10px </style> 

Let's see the result in the browser, making sure that everything works with the help of vue-devtools:


image


As you can see, we have all the application logic ready! We enter the user name, the request is executed and the profile data is stored in the store.


User Profile


Create a User.vue component and add logic to indicate loading, profile display, and an error when the user is not found. Also add a transition animation.


User.vue
 <template> <div class="github-card"> <transition name="fade" mode="out-in"> <div v-if="loading" key="loading"> Loading </div> <div v-else-if="user" key="user"> <div class="background" :style="{backgroundImage: `url(${user.avatar_url})`}" /> <div class="content"> <a class="avatar" :href="`https://github.com/${user.login}`" target="_blank"> <img :src="user.avatar_url" :alt="user.login" /> </a> <h1>{{user.name || user.login}}</h1> <ul class="status"> <li> <a :href="`https://github.com/${user.login}?tab=repositories`" target="_blank"> <strong>{{user.public_repos}}</strong> <span>Repos</span> </a> </li> <li> <a :href="`https://gist.github.com/${user.login}`" target="_blank"> <strong>{{user.public_gists}}</strong> <span>Gists</span> </a> </li> <li> <a :href="`https://github.com/${user.login}/followers`" target="_blank"> <strong>{{user.followers}}</strong> <span>Followers</span> </a> </li> </ul> </div> </div> <div v-else key="not-found"> User not found </div> </transition> </div> </template> <script> import {mapState} from 'vuex'; export default { name: 'User', computed: mapState(['loading', 'user']) }; </script> <style lang="stylus" scoped> .github-card margin-top 50px padding 20px text-align center background #fff color #000 position relative h1 margin 16px 0 20px line-height 1 font-size 24px font-weight 500 .background filter blur(10px) opacity(50%) z-index 1 position absolute top 0 left 0 right 0 bottom 0 background-size cover background-position center background-color #fff .content position relative z-index 2 .avatar display inline-block overflow hidden background #fff border-radius 100% text-decoration none img display block width 80px height 80px .status background white ul text-transform uppercase font-size 12px color gray list-style-type none margin 0 padding 0 border-top 1px solid lightgray border-bottom 1px solid lightgray zoom 1 &:after display block content '' clear both li width 33% float left padding 8px 0 box-shadow 1px 0 0 #eee &:last-of-type box-shadow none strong display block color #292f33 font-size 16px line-height 1.6 a color #707070 text-decoration none &:hover color #4183c4 .fade-enter-active, .fade-leave-active transition opacity .5s .fade-enter, .fade-leave-to opacity 0 </style> 

App.vue connect our component in App.vue and enjoy the result:


App.vue
 <template> <div id="app"> <Search /> <User /> </div> </template> <script> import Search from './components/Search'; import User from './components/User'; export default { name: 'app', components: { User, Search } }; </script> <style lang="stylus"> #app font-family 'Avenir', Helvetica, Arial, sans-serif font-smoothing antialiased margin 10px </style> 

image


Tests


We write simple tests for our application.


tests / e2e / specs / test.js


 describe('Github User Search', () => { it('has input for username', () => { cy.visit('/'); cy.get('input'); }); it('has "User not found" caption', () => { cy.visit('/'); cy.contains('User not found'); }); it("finds Linus Torvalds' GitHub page", () => { cy.visit('/'); cy.get('input').type('torvalds'); cy.contains('Linus Torvalds'); cy.get('img'); cy.contains('span', 'Repos'); cy.contains('span', 'Gists'); cy.contains('span', 'Followers'); }); it("doesn't find nonexistent page", () => { cy.visit('/'); cy.get('input').type('_some_random_name_6m92msz23_2'); cy.contains('User not found'); }); }); 

Run the tests with the command


 npm run test:e2e 

In the window that opens, click the Run all specs button and see that the tests pass:


image


Assembly


Vue CLI 3 supports the new application build mode, modern mode. It creates 2 versions of scripts: lightweight for modern browsers that support the latest JavaScript features, and the full version with all the necessary polyfiles for older ones. The main charm is that we absolutely do not need to bother with the deployment of such an application. It just works. If the browser supports <script type="module"> , it will tighten the lightweight build itself. How it works, you can read more in this article .


Add the modern flag to the package.json to the build command:


 "build": "vue-cli-service build --modern" 

We collect the project:


 npm run build 

Let's look at the sizes of the final scripts:


 8.0K ./app-legacy.cb7436d4.js 8.0K ./app.b16ff4f7.js 116K ./chunk-vendors-legacy.1f6dfb2a.js 96K ./chunk-vendors.a98036c9.js 

As you can see, the new method really reduces the size of the assembly. The difference will be even more noticeable on large projects, so the feature definitely deserves attention.


Code


Github


Demo


That's all, thank you for your attention!


')

Source: https://habr.com/ru/post/420351/


All Articles