fbpx

Building Medium Clone with Vue 3

Building Medium Clone with Vue 3

 

This is the beginning of an exciting tutorial that will introduce you to Vue version 3 along with most of its workings. We are going to create a Medium-like clone in this tutorial series learning all the basics of the latest Vue framework. The tutorial will start with the basics of creating a Vue version 3 project using Vue CLI. Then, move towards the implementation of UI for header, footer, and different screens.

We are going to learn navigation using Vue Router as well as use the Vuex store management tool for the user authentication and fetching article feed data and user info data. The tutorial will introduce you to the UI template building first and then bind the dynamic data to the templates. At the end of this tutorial series, you will have a medium clone built using Vue version 3 with ample understanding of Vue Router and Vuex store management.

 

Chapter 1: Building Medium Clone with Vue 3 | Getting Started.

This is the first chapter of the medium clone tutorial series. In this chapter, we are going through the installation of vue CLI to starting a new Vue 3 project. We are also going to initialize the Vue router in order to navigate to different pages. The idea is to get started with the started Vue 3 template, organize the required file structure, then implement the Navbar component through which we can navigate to different web pages.

So, let’s get started!

Install Vue CLI

Here, we are going to install the latest version of Vue CLI globally in our system. It allows us to download the starter Vue template based on various configurations. The required packages are pre-installed. It also enables us to manage the Vue project. In order to install it, we need to run the following command in our system terminal:

npm install –g @vue/cli

Create Vue project

After Vue CLI has been installed, we can now start a new Vue Project. For that, we need to run the following command in the directory where we want to download the project:

vue create medium-clone

Remember that this command allows us to select from three different options. Here, we need to select the manual configuration and then select the required libraries such as Babel, ESLint, Vuex, Vue Router, etc. with Vue Version 3.

After then successful installation of the project, we need to go to the project directory using the following command:

cd medium-clone

Then, we need to run in order to run the project in the localhost:

npm run serve

Hence, we will get the following result in the browser:

Organizing Project Files

Now, our task is to remove all the default Vue files. Then, we need to create our own files that are Home.vueLogin.vue, and Register.vue.

Next, we need to create a component file named NavBar.vue inside the ./components folder.

We also need to create a main.css file inside the ./assets folder.

Hence, our project folder structure will look as shown in the screenshot below:

 

Downloading CSS

Here, we are going to make use of Bootstrap version 4 CSS styles. For that, we can copy all the CSS styles from the URL and paste it into the main.css file.

In order to use the styles from main.css, we need to import it to our main.js file:

import './assets/main.css';

Creating Navbar component

Now, we are going to create a simple Navbar component using the basic HTML elements and styles from main.css. We are going to have a Navbar logo for medium clone on the left and a horizontal list of navigation links to the right. The overall template code for the Navbar.vue component is provided in the code snippet below:

<template> 
 <nav class="navbar navbar-light">    
   <div class="container">      
     <a class="navbar-brand" href="index.html">Medium Clone</a>      
     <ul class="nav navbar-nav pull-xs-right">        
      <li class="nav-item">          
       <a class="nav-link active" href="">Home</a>        
      </li>        
      <li class="nav-item">          
       <a class="nav-link" href="">           
        <i class="ion-compose"></i>&nbsp;New Post  
       </a>        
      </li>       
      <li class="nav-item">         
       <a class="nav-link" href="">
         <i class="ion-gear-a"></i>&nbsp;Settings
       </a>
      </li>
      <li class="nav-item">
       <a class="nav-link" href="">Sign up</a>
      </li>
     </ul>
    </div>
  </nav>                  
</template>

Now, we need to import the NavBar component to App.vue as NavBar as shown in the code snippet below:

import NavBar from "./components/NavBar.vue";

Then, we need to register the NavBar component to the components object in the App.vue file:

<template>
 <div>
     <NavBar />
     <router-view />
  </div>
</template> 
<script> 
import NavBar from "./components/NavBar.vue"; 
export default {
 components : {
     NavBar
  } 
} 
</script>

Hence, we will get the NavBar at the top of the webpage as shown in the browser window below:

Now, we deal with the Vue router.

Implementing Vue Router

Vue Router allows us to define routes for different webpages and helps to manage them easily. Here, we have an index.js file inside the ./router folder. In order to use Vue Router in Vue 3, we need to import createRouter module from the vue-router package that has been preinstalled. We also need to import all the view files in order to configure routes. The overall configuration of the Vue router is provided in the code snippet below:

import { createRouter, createWebHashHistory } from "vue-router"; 
import Home from "../views/Home.vue"; 
import Login from "../views/Login.vue"; 
import Register from "../views/Register.vue"; 
const routes = [
 {
     path: "/",
     name: "Home",
     component: Home 
  }, 
  {
      path: "/login",    
      name: "Login",
      component: Login 
   }, 
   {    
      path: "/register",
      name: "Register",
      component: Register 
    }, 
]; 
const router = createRouter({ 
  history: createWebHashHistory(),
  routes 
}); 
export default router;

Now, we need to create simple templates for Login and Register in the Login.vue and Register.vue files.

In Login.vue,

<template>
 <div>
     <h1>Login</h1>
 </div> 
</template> 
<script> 
export default { } 
</script>

In Register.vue,

<template>
 <div>    
   <h1>Register</h1>
 </div> 
</template> 
<script> 
export default { } 
</script>

Next in NavBar.vue, we need to add the navigation links provided by Vue Router as shown in the code snippet below:

<template>
  <nav class="navbar navbar-dark bg-inverse">
    <div class="container">
      <a class="navbar-brand" href="index.html">Medium Clone</a>
      <ul class="nav navbar-nav pull-xs-right">
        <li class="nav-item">
          <router-link class="nav-link" :to="{name : 'Home'}">Home</router-link>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="">
            <i class="ion-compose"></i>&nbsp;New Post
          </a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="">
            <i class="ion-gear-a"></i>&nbsp;Settings
          </a>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name : 'Login'}">Sign In</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name : 'Register'}">Register</router-link>
        </li>
      </ul>
    </div>
  </nav>
</template>

Hence, we can now navigate to different webpages using the nav links in the Navbar as demonstrated in the demo screenshot below:

Finally, we have our Vue project with views, Navbar, and Vue router along with routes configured.

Conclusion

This tutorial chapter is the beginning of our Vue Medium Clone tutorial series. There are many more UI and feature implementation to come in the upcoming chapters. We have set up the vue router and respective views in the chapter. We were able to navigate to different webpages as well using Nav links from the Navbar.

Now, in the next chapter, we are going to implement the UI for Login, Register, and Home Screen.

Chapter 2: Building Medium Clone with Vue 3 | Login, Register, and Home Screen

In this chapter of our medium clone tutorial series, we are going to implement the basic UI for the Login, Register, and Home Screen. The idea is to start by adding a simple footer at the bottom and then move on to the screens.

Let’s get started!

Adding Footer

Here, we are going to add a simple Footer that will be displayed at the bottom of each page. For that, we need to create a file named Footer.vue in the ./components folder.

To create a simple footer template, we can use the code from the following code snippet:

<template>
  <footer>
    <div class="container">
      <router-link to="/" class="logo-font">Medium Clone</router-link>
      <span class="attribution">
        A Real World Vue project from <a href="https://kriss.io">Kriss</a>.
      </span>
    </div>
  </footer>
</template>

styles changed a little:

footer {
  background: #373a3c;
  margin-top: 3rem;
  padding: 1rem 0;
  position: absolute;
  bottom: 0;
  width: 100%;
}
footer .logo-font {
  vertical-align: middle;
}
footer .attribution {
  vertical-align: middle;
  margin-left: 10px;
  font-size: 0.9rem;
  color: #FFF;
  font-weight: 300;
}

To display the footer at the end of every page, we need to import the Footer component in the App.vue file as shown in the code snippet below:

import Footer from "./components/Footer.vue";

Then, we need to register it to components object and use it below the router element as directed in the code snippet below:

<template>
  <div>  
    <NavBar />
    <router-view />
    <Footer />
  </div>
</template>

<script>
import NavBar from "./components/NavBar.vue";
import Footer from "./components/Footer.vue";


export default {
  components : {
    NavBar,
    Footer
  }
}
</script>

Hence, we will get the result as shown in the screenshot below:

As we can notice, we got a footer at the bottom.

Create Register Screen

Now, we move on to Register Screen which we left with a simple template before. Now, we are going to implement the overall template for Register Screen in Register.vue file which will have a form and a button. For that, we can use the code from the following code snippet:

<template>
  <div class="auth-page">
    <div class="container page">
      <div class="row">

        <div class="col-md-6 offset-md-3 col-xs-12">
          <h1 class="text-xs-center ">Sign Up</h1>
          <p class="text-xs-center">
            <router-link to="/login">Have an account?</router-link>
          </p>

          <ul class="error-messages">
            <li style="list-style: none">That email is already taken</li>
          </ul>

          <form>
            <fieldset class="form-group">
              <input class="form-control form-control-lg" type="text" placeholder="Your Name">
            </fieldset>
            <fieldset class="form-group">
              <input class="form-control form-control-lg" type="text" placeholder="Email">
            </fieldset>
            <fieldset class="form-group">
              <input class="form-control form-control-lg" type="password" placeholder="Password">
            </fieldset>
            <button class="btn btn-lg btn-secondary pull-xs-right">
              Sign up
            </button>
          </form>
        </div>

      </div>
    </div>
  </div>
</template>

Hence, the Register Screen appears as displayed in the browser window screenshot below:

Create Login Screen

Login Screen will appear similar to Register Screen with fewer number of form input fields. The template code for Login Screen is provided in the code snippet below:

<template>
  <div class="auth-page">
    <div class="container page">
      <div class="row">

        <div class="col-md-6 offset-md-3 col-xs-12">
          <h1 class="text-xs-center">Sign In</h1>
          <p class="text-xs-center">
            <router-link to="/register">Need an Account?</router-link>
          </p>

          <ul class="error-messages">
            <li style="list-style: none">That email is already taken</li>
          </ul>

          <form>
            <fieldset class="form-group">
              <input class="form-control form-control-lg" type="text" placeholder="Email">
            </fieldset>
            <fieldset class="form-group">
              <input class="form-control form-control-lg" type="password" placeholder="Password">
            </fieldset>
            <button class="btn btn-lg btn-secondary pull-xs-right">
              Sign In
            </button>
          </form>
        </div>

      </div>
    </div>
  </div>
</template>

Hence, the Login Screen will appear as shown in the demo below:

Notice that, we removed the Login option from Navbar. We have kept the nav link to navigate to the login screen inside the Register screen.

Create Home Screen

Here, we are going to implement the Home Screen view. It will have a banner at the top, a tab view below the banner. Then, there will be a display of article posts inside the tabs. On the right side of the screen, we will add some popular tags sections as well. For now, we are going to use the mock data to popular the article and popular tags section.

Adding Banner and Tabs

First, we are going to add a banner and tab view to the Home screen. For that, we need to make use of code from the following code snippet:

<template>
  <div class="home-page">

    <div class="banner">
      <div class="container">
        <h1 class="logo-font">Medium Clone</h1>
        <p>Get close in personal with great ideas.</p>
      </div>
    </div>

    <div class="container page">
      <div class="row">

        <div class="col-md-9">
          <div class="feed-toggle">
            <ul class="nav nav-pills outline-active">
              <li class="nav-item">
                <a class="nav-link disabled" href="">Your Feed</a>
              </li>
              <li class="nav-item">
                <a class="nav-link active" href="">Global Feed</a>
              </li>
            </ul>
          </div>

        </div>

      </div>
    </div>

  </div>
</template>

Hence, we will get a simple banner along with a Tab view in the body of the Home Screen:

Adding Article Post Previews

Now, we are going to add the section to display the article post previews just below the tab element. For that, we need to add the code provided in the code snippet below:

<div class="container page">
      <div class="row">

        <div class="col-md-9">
          <div class="feed-toggle">
            <ul class="nav nav-pills outline-active">
              <li class="nav-item">
                <a class="nav-link disabled" href="">Your Feed</a>
              </li>
              <li class="nav-item">
                <a class="nav-link active" href="">Global Feed</a>
              </li>
            </ul>
          </div>

          <div class="article-preview">
            <div class="article-meta">
              <a href="profile.html"><img src="http://i.imgur.com/Qr71crq.jpg" /></a>
              <div class="info">
                <a href="" class="author">Kriss Kawa</a>
                <span class="date">February 20th</span>
              </div>
              <button class="btn btn-outline-primary btn-sm pull-xs-right">
                <i class="ion-heart"></i> 28
              </button>
            </div>
            <a href="" class="preview-link">
              <h1>How to build robust and dynamic React Native apps</h1>
              <p>Learn about basics of React Native app development</p>
              <span>Read more...</span>
            </a>
          </div>

          <div class="article-preview">
            <div class="article-meta">
              <a href="profile.html"><img src="http://i.imgur.com/N4VcUeJ.jpg" /></a>
              <div class="info">
                <a href="" class="author">Gaza Lang</a>
                <span class="date">March 20th</span>
              </div>
              <button class="btn btn-outline-primary btn-sm pull-xs-right">
                <i class="ion-heart"></i> 10
              </button>
            </div>
            <a href="" class="preview-link">
              <h1>The mark of good web developer.</h1>
              <p>This is the description for the post.</p>
              <span>Read more...</span>
            </a>
          </div>

        </div>

   </div>
</div>

Hence, we will get the article post previews as displayed in the browser screenshot below:

Adding Popular tags section

Now, we are going to add the Popular Tags section on the right side of the screen. For that, we need to add the code below just below the articles section:

<div class="col-md-3">
          <div class="sidebar">
            <p><strong>Popular Tags</strong></p>

            <div class="tag-list">
              <a href="" class="tag-pill tag-default">mobiledevelopment</a>
              <a href="" class="tag-pill tag-default">react</a>
              <a href="" class="tag-pill tag-default">reactnative</a>
              <a href="" class="tag-pill tag-default">vue</a>
              <a href="" class="tag-pill tag-default">sql</a>
              <a href="" class="tag-pill tag-default">angular</a>
              <a href="" class="tag-pill tag-default">nodejs</a>
              <a href="" class="tag-pill tag-default">webdevelopment</a>
              <a href="" class="tag-pill tag-default">flutter</a>
              <a href="" class="tag-pill tag-default">dart</a>
              <a href="" class="tag-pill tag-default">cryto</a>
            </div>
          </div>
</div>

Hence, we will get the popular tags section at the right side of the screen as displayed in the browser screenshot below:

With this, our Home Screen UI is complete for now, We are going to populate the article section and popular tags section with actual data from the API in the later.

Create Settings Screen

Here, we are going to create a simple Settings Screen which will have a simple form similar to the Register screen. For that, we need to create a Settings.vue file inside the ./views folder first. Then, we need to make use of the code provided in the code snippet below:

<template>
  <div class="settings-page">
    <div class="container page">
      <div class="row">

        <div class="col-md-6 offset-md-3 col-xs-12">
          <h1 class="text-xs-center">Settings</h1>

          <form>
            <fieldset>
                <fieldset class="form-group">
                  <input class="form-control" type="text" placeholder="Profile Image URL">
                </fieldset>
                <fieldset class="form-group">
                  <input class="form-control form-control-lg" type="text" placeholder="Name">
                </fieldset>
                <fieldset class="form-group">
                  <textarea class="form-control form-control-lg" rows="8" placeholder="Description"></textarea>
                </fieldset>
                <fieldset class="form-group">
                  <input class="form-control form-control-lg" type="text" placeholder="Email">
                </fieldset>
                <fieldset class="form-group">
                  <input class="form-control form-control-lg" type="password" placeholder="Password">
                </fieldset>
                <button class="btn btn-lg btn-secondary pull-xs-right">
                  Update
                </button>
            </fieldset>
          </form>
        </div>

      </div>
    </div>
  </div>
</template>

We have not yet registered the Settings screen route to our Vue Router. Hence, we need to import the Settings Screen inside the index.js file of the ./router directory:

import Settings from "../views/Settings.vue";

Then, we need to define the route object for Settings Screen inside the routes array variable:

{
    path: "/settings",
    name: "Settings",
    component: Settings
},

Then, we need to change the Setting link element in the Navbar to router link as directed in the code snippet below:

<li class="nav-item">
          <router-link class="nav-link" :to="{name : 'Settings'}">
            <i class="ion-gear-a"></i>&nbsp;Settings
          </router-link>
</li>

As a result, we will be able to navigate to the Settings screen which will appear as shown in the browser screenshot below:

Conclusion

In this part two of our medium clone tutorial series, we implemented a footer for all screens as well as implemented the UI for required screens. The bonus part was the implementation of the Settings screen.

In the next chapter, we are going to implement user authentication using Vuex.

Chapter 3: Building Medium Clone with Vue 3 | User Authentication

 

User authentication with Vuex

In this chapter, we are going to implement the user login and authentication using the Vuex store mechanism. Vuex is a centralized store where our user as well as article data will reside. The state in the store can be fetched to any screens or components inside a vue project. Any Vuex store will have a state, mutations, and actions. Only mutations can access the state variables using the commit function. And actions are the functions that trigger the mutations. In this chapter, we are going to implement a similar flow for login as well as authentication. For login purposes, we are going to make an HTTP request to the RestAPI using the Axios library.

The API endpoint is provided below:

https://conduit.productionready.io/api

We are going to use this API base URL to fetch the user data as well as article data later on.

So, let’s get started!

Installing Axios and configuring it

First of all, we need to install the Axios package. This package allows us to make HTTP requests to the server in turn returning a promise. It offers easy handling of the HTTP request with all the header configurations as well. Now to install it, we need to run the following command in our project directory:

npm install axios

Now, we are going to configure the axios module inside a separate file called api.js. Hence, we need to create a file called api.js inside the ./store directory. Inside the file, we are going to configure the baseURLoption as shown in the code snippet below:

import axios from "axios";

export const api = axios.create({
  baseURL: "https://conduit.productionready.io/api"
});

export function setToken(jwt) {
  api.defaults.headers.common["Authorization"] = `Token ${jwt}`;
}

export function clearToken() {
  delete api.defaults.headers.common["Authorization"];
}

Here, we have configured two functions as well. One in order to set the authorization token header after the login and another to remove the authorization token in case of a logout state or new login.

Creating User module for Vuex Store

Now, we are going to create a user module that we can call a reducer for our Vuex store. For that, we need to create a folder called ./modules inside the ./store folder. And inside the ./modules folder, we need to create a file called users.js. This users.js file will hold the centralized store state for user objects as well as profiles. Now, inside the users.js file, we can use the code from the following code snippet:

import { api, setToken, clearToken } from "../api";
export default {
  namespaced: true,
  state: {
    user: null,
    profile: null
  },
  getters: {
    username(state) {
      return (state.user && state.user.username) || null;
    }
  },
  mutations: {
    setUser(state, payload) {
      state.user = payload;
    }
  },
  actions: {
    loginUser: async function({ commit }, { email, password }) {
      clearToken();
      try {
        const response = await api.post("/users/login", {
          user: {
            email,
            password
          }
        });
        if (response.data.user) {
          setToken(response.data.user.token);
          commit("setUser", response.data.user);
        }
      } catch (e) {
        console.error(e);
        throw e;
      }
    }
  }
};

Here, we have two store states: user object to contain the user information and profile object to contain the information of other users’ profiles.  To set the user object, we have a mutation function called setUser. We also have an action called loginUser which has all the login configurations. We have imported the api, setToken and clearToken from api.js in order to use while processing login. Using axios post request, we have requested an authorization token and a user object from the server. After the successful response, we have set the user object as well as the authorization token in the api.js file.

Now, we need to register our users.js module in the main Vuex store file i.e. index.js file inside ./store folder.

First, we need to import the users.js module as users as shown in the code snippet below:

import users from "./modules/users";

Then, we need to define the users module inside the modules option of createStore function:

import { createStore } from "vuex";

import users from "./modules/users";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    users
  }
});

Now, the state and actions inside the users module will be accessible on any page of the project.

Login configuration

Here, we are going to implement the login functionality using the action from the same users module. For that, we need to define some variables and methods inside the Login.vue file as shown in the code snippet below:

<script>
export default {
  data: function() {
    return {
      password: "",
      email: "",
      errors: []
    };
  },
  methods: {
    login() {
      this.$store
        .dispatch("users/loginUser", {
          email: this.email,
          password: this.password
        })
        .then(() => {
          this.errors = [];
          this.$router.replace({name : "Home"});
        })
        .catch(err => {
          this.errors.push(err);
        });
    }
  }
};
</script>

Here, we have three state variables: email, passwordand error array. We also have a function called login where we have called the loginUser action inside the users module using dispatch method from the store instance. After the successful login, we redirect the user to the home page.

The next step is to bind these variables as well as login function to the login template we had created before. For that, you can check the code from the following code snippet:

<template>
  <div class="auth-page">
    <div class="container page">
      <div class="row">
        <div class="col-md-6 offset-md-3 col-xs-12">
          <h1 class="text-xs-center">Sign in</h1>
          <p class="text-xs-center">
            <router-link to="/register">Need an Account?</router-link>
          </p>

          <ul class="error-messages">
            <li v-for="(error, i) in errors" :key="i">{{ error.message }}</li>
          </ul>

          <form>
            <fieldset class="form-group">
              <input
                v-model="email"
                class="form-control form-control-lg"
                type="text"
                placeholder="Email"
              />
            </fieldset>
            <fieldset class="form-group">
              <input
                v-model="password"
                class="form-control form-control-lg"
                type="password"
                placeholder="Password"
              />
            </fieldset>
            <button @click="login" class="btn btn-lg btn-secondary pull-xs-right">
              Sign In
            </button>
          </form>
        </div>
      </div>
    </div>
  </div>
</template>

For now, you can test the login feature using the following credentials:

email: testing001@test.com
password: testtest

Hence, we can see the result of the login operation from the demo below:

We also get the user object as a response which logged as shown in the screenshot below:

As we get the user object, the user information is automatically set to the user state in the store as well from the loginUser action. At the same time, the authorization token is set as well.

Updating UI based on User Authentication

Now since we have the user object as well as token, we can show the UI based on that. It is common sense to hide the register and sign up options from the Navbar once the user is logged in. Instead of showing these options, we are going to render out the logged-in users’ username. For that, we need to get the users username first, for that, we are going to create a new action function inside the user.js file called getUser. The coding implementation is provided in the code snippet below:

getUser: async function({ commit }) {
  const user = await api.get("/user");
  commit("setUser", user);
},

Now in the NavBar.js file, we need to fetch the username of the user inside the computed method as shown in the code snippet below:

<script>
export default {
  computed: {
    username() {
      return this.$store.getters["users/username"];
    }
  }
};
</script>

Now, the username of the user is available in the username() method. Using this computed method, we can now apply conditional rendering to the Navbar options as shown in the code snippet below:

<li v-if="username == null" class="nav-item">
  <router-link class="nav-link" to="/register"> Register </router-link>
</li>
<li v-if="username" class="nav-item">
  <router-link class="nav-link" :to="`/@${username}`">
    {{ username }}
  </router-link>
</li>

Hence, we will get the result as shown in the demo below:

As we can see, the register option vanishes, and the username of the user is shown as soon as the user logins.

Implementing Logout

Since we can log in now, we need to create a feature to logout of it as well. The logout operation is simple. We just need to clear out the authorization token from the axios option as well as set the user object to null in the users module. For that, we need to create a new action method inside the user.js called logoutUser. This method will call the clearToken function to clear out the authorization token as well as set the user object to null calling the setUser mutation. The coding implementation is provided in the code snippet below:

logoutUser: async function({ commit }) {
  clearToken();
  commit("setUser", null);
},

Now in the Navbar.js component, we need to add a logout option as well. We are going to render the logout only when the user is logged in so the conditional rendering applies to it as well:

<li v-if="username" class="nav-item" @click="logout">
   <router-link class="nav-link" to="/"> Log Out </router-link>
</li>

Here, we have also called the logout function whose implementation is provided in the code snippet below:

methods : {
    logout : function(){
      this.$store.dispatch("users/logoutUser");
    }
  }

We simply dispatched the logoutUser action inside the user.js.

Hence, the end result is demonstrated in the demo screenshot below:

Finally, we have successfully implemented the login and logout feature using the Vuex mechanism in our medium clone project.

Conclusion

In this third chapter of our medium clone tutorial series, we got to learn the Vuex store mechanism in detail. The overall user authentication process was implemented using Vuex state, mutation, and actions. The major focus of this tutorial was to guide readers on the use of the Vuex mechanism on Vue 3.

In the next chapter, we are going to fetch the dynamic data for the Article list and implement the Profile page as well.

 

Chapter 4: Building Medium Clone with Vue 3 | Implement Dynamic Article List and Profile Page

In this previous chapter, we figured out the login and logout in our medium clone using the Vuex mechanism. We are going to continue from where we left off. In this chapter, we are going to fetch the article data dynamically and show it on the home screen. We are going to apply the Vuex mechanism to it as well. We are also going to implement the dynamic tab selection section of the Home screen. Finally, we are going to end this tutorial series with the implementation of the Profile page screen.

So, let’s get started!

Separate Article Preview component

Before fetching data for articles, we are going to implement a separate component for it. For that, we need to create a new component file called ArticlePreview.vue in the ./components folder.

Inside the file, we can use the same code that we used for implementing the articles list section on the Home screen. However, the code for the ArticlePreview component is provided in the code snippet below:

<template>
  <div class="article-preview">
    <div class="article-meta">
      <a href="profile.html"><img src="http://i.imgur.com/Qr71crq.jpg" /></a>
      <div class="info">
        <a href="" class="author">Kriss Kawa</a>
        <span class="date">February 20th</span>
      </div>
      <button class="btn btn-outline-primary btn-sm pull-xs-right">
        <i class="ion-heart"></i> 28
      </button>
    </div>
    <a href="" class="preview-link">
      <h1>How to build robust and dynamic React Native apps</h1>
      <p>Learn about basics of React Native app development</p>
      <span>Read more...</span>
    </a>
  </div>
</template>

Next, we need to import the ArticlePreview component to the Home screen as shown in the code snippet below:

import ArticlePreview from '../components/ArticlePreview'

Then, we need to register the component to components object as shown in the code snippet below:

export default {
  name: "Home",
  components: {
    ArticlePreview
  }
};

Lastly, we need to use the component in the template in place of the article section code:

<div class="col-md-9">
          <div class="feed-toggle">
            <ul class="nav nav-pills outline-active">
              <li class="nav-item">
                <a class="nav-link disabled" href="">Your Feed</a>
              </li>
              <li class="nav-item">
                <a class="nav-link active" href="">Global Feed</a>
              </li>
            </ul>
          </div>

          <ArticlePreview/>
          <ArticlePreview/>
            

        </div>

Hence, we will get the result as shown in the screenshot below:

Fetching Article data using Vuex

In this step, we are going to fetch the article data from the API mentioned in the api.js file. After fetching the data, we are going to feed the data into the ArticlePreview component from the home screen to display the dynamic article list. All the fetching operation will be done using the Vuex mechanism.

First, we need to create a new Vuex module called article.js inside the ./modules folder.

Inside the article.js file, we can use the code from the following code snippet:

import { api } from "../api";
export default {
  namespaced: true,
  state: {
    feed: [],
    count: 0
  },
  mutations: {
    setArticles(state, { articles, articlesCount }) {
      state.feed = articles;
      state.count = articlesCount;
    }
  },
  actions: {
    async getGlobalFeed({ commit }, payload = { page: 1 }) {
      let route = "/articles";
      if (payload) {
        const {
          tag = null,
          author = null,
          favourited = null,
          page = 1
        } = payload;
        route += tag ? `?tag=${tag}&` : "";
        route += author ? `?author=${author}&` : "";
        route += favourited ? `?favourited=${favourited}&` : "";
        route += page ? `?offset=${(page - 1) * 10}&limit=10` : "";
      }
      const response = await api.get(route);
      commit("setArticles", response.data);
    },
    async getUserFeed({ commit }, payload = { page: 1 }) {
      let route = "/articles/feed";
      if (payload) {
        const { page = 1 } = payload;
        route += page ? `?offset=${(page - 1) * 10}&limit=10` : "";
      }
      const response = await api.get(route);
      commit("setArticles", response.data);
    }
  }
};

Here, we have two states: feed and count. The feed state will hold all the article data whereas the count will hold the number of articles. We have got a mutation called setArticles which sets the article feed data and count to the states. Then, we have two actions called getGlobalFeed and getUserFeed. The getGlobalFeed function fetches the global article list data using axios get request and sets it to the feed state using the setArticles mutation. The getUserFeed function does the same thing but sets the user article feed to the feed state.

Now, we need to register this module to the root Vuex store. We need to import the module to index.js of ./store directory and set articles module to modules object in createStore instance:

import { createStore } from "vuex";

import users from "./modules/user";
import articles from "./modules/articles";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    users,
    articles
  }
});

Our Vuex operation ends here. Now, we simply use the actions to fetch the data and use the article feeds anywhere in the Vue project.

Fetching Article Feed based on Tabs

In this step, we are going to fetch the articles feed in the Home screen based on the tabs. If the tab is on the ‘Your feed’ option then, we are going to fetch the own article feed dispatching the action getUserFeed. And if the tab is on the ‘Global’ option then, we are going to fetch all the articles dispatching the action getGlobalFeed.

But first, we are going to implement the UI section. We are going to show the ‘Your feed’ option only when the user is logged in. Hence, we need to use the conditional rendering here. The overall coding implementation is provided in the code snippet below:

<div class="feed-toggle">
   <ul class="nav nav-pills outline-active">
      <li class="nav-item">
        <a
          class="nav-link"
          v-if="username"
          @click="setFeed('user');"
          :class="{ active: activeFeed === 'user' }"
         >
            Your Feed
         </a>
       </li>
       <li class="nav-item">
        <a
           class="nav-link"
           @click="setFeed('global');"
           :class="{ active: activeFeed === 'global' }"
         >
           Global Feed
         </a>
       </li>
      </ul>
   </div>

Here, we have called the setFeed method. The setFeed method is used to fetch the article feed based on the tabs option.

The implementation of setFeed function is provided in the code snippet below:

export default {
  components: {
    ArticlePreview
  },
  data: function() {
    return {
      activeFeed: "global"
    };
  },
  methods: {
    setFeed(feedType) {
      if (feedType === "global") {
        this.activeFeed = "global";
        this.$store.dispatch("articles/getGlobalFeed");
      } else if (feedType === "user") {
        this.activeFeed = "user";
        this.$store.dispatch("articles/getUserFeed");
      }
    }
  },
  created() {
    this.setFeed("global");
  },
  computed: {
    username() {
      return this.$store.getters["users/username"];
    }
  },
};

Based on the feedType value, we are dispatching the action. We have also called the setFeed function inside the created method so that the global article feed is fetched when the Home screen is first launched. The username is also fetched in the computed method for the conditional rendering.

Now, we can fetch the articles to the Home screen from the Vuex article state. For that, we are going to use the computed object as shown in the code snippet below:

computed: {
    globalArticles() {
      return this.$store.state.articles.feed || [];
    },
    username() {
      return this.$store.getters["users/username"];
    }
  },

Since we have the articles feed data in the Home screen, we can now feed it to ArticlePreview the component. But first, we need to implement the ArticlePreview component to be able to take in the dynamic data.

In ArticlePreview component, we are going to display the date and time value as well. We are getting the timestamp value from the API. Hence, we need to format the date and time using the moment.js package.

First, we need to install it in our project. For that, we need to run the following command:

npm install moment

The ArticlePreview component template with dynamic data binding is provided in the code snippet below:

<template>
  <div class="article-preview">
    <div class="article-meta">
      <a href="profile.html"><img :src="article.author.image"/></a>
      <div class="info">
        <a href="" class="author">{{ article.author.username }}</a>
        <span class="date">{{ formatDate(article.createdAt) }}</span>
      </div>
      <button class="btn btn-outline-primary btn-sm pull-xs-right">
        <i class="ion-heart"></i> {{ article.favoritesCount }}
      </button>
    </div>
    <router-link :to="`/article/${article.slug}`" class="preview-link">
      <h1>{{ article.title }}</h1>
      <p>{{ article.description }}</p>
      <span>Read more...</span>
    </router-link>
  </div>
</template>

<script>
import moment from "moment";
export default {
  props: ["article"],
  methods: {
    formatDate(dateString) {
      return moment(dateString).format("MMMM Do, YYYY");
    }
  }
};
</script>

Here, we have used the moment library module inside the formatDate method to get the proper format of the date. Now, the ArticlePreview component is ready to get dynamic article feed prop data from the Home screen. In order to send the props to the ArticlePreview component, we can use the code from the following code snippet on the Home screen:

<ArticlePreview
   v-for="article in globalArticles"
   :key="article.slug"
   :article="article"
></ArticlePreview>

Hence, we will get the article feed as shown in the demo below:

The user feed is empty because we have not yet created any user posts. Hence, only the global data feed is available.

Implementing Profile page

Now, we are going to implement the Profile page for the logged-in user. The profile screen will have a User info section and Article list section. But first, we need to create a new Profile.vue file in the ./views folder.

Then, we need to add the Profile screen to our routes. For that, we need to goto the index.js file of the ./router folder and import the Profile screen:

import Profile from "../views/Profile.vue";

Then, we need to register the profile screen to the routes object:

{
    path: "/:username",
    name: "profile",
    component: Profile
}

Here, we have the dynamic username binding in the path. This indicates that the path URL to the profile page will be different for each user based on their username.

User Info section

Inside the Profile.vue file, we are going to implement a User Info section first. It will have a user image along with a name and description. The code for this is available in the code snippet below:

<template>
  <div class="profile-page">
    <div class="user-info">
      <div class="container">
        <div class="row">
          <div class="col-xs-12 col-md-10 offset-md-1">
            <img src="https://i.imgur.com/wAoYezk.jpg" class="user-img" />
            <h4>Kriss Kawa</h4>
            <p>
              Founder at Kriss, Developer, Content Writer
            </p>
          </div>
        </div>
      </div>
    </div>
</template>

Hence, the navigation to the profile screen along with the User Info section is demonstrated in the demo below:

Tabs and Articles

Below the User Info section, we are going to implement the Tabs and Articles section. It is similar to that of the Home screen which has the tab options and the article feed. Here, we are not going to feed the dynamic data but implement static article feed data since we don’t have any user feed. The template for the Tabs and Articles section is provided in the code snippet below:

<div class="container">
      <div class="row">
        <div class="col-xs-12 col-md-10 offset-md-1">
          <div class="articles-toggle">
            <ul class="nav nav-pills outline-active">
              <li class="nav-item">
                <a class="nav-link active" href="">My Articles</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="">Favorites</a>
              </li>
            </ul>
          </div>

          <div class="article-preview">
            <div class="article-meta">
              <a href="profile.html"><img src="https://i.imgur.com/wAoYezk.jpg" /></a>
              <div class="info">
                <a href="" class="author">Kriss Kawa</a>
                <span class="date">February 20th</span>
              </div>
              <button class="btn btn-outline-primary btn-sm pull-xs-right">
                <i class="ion-heart"></i> 28
              </button>
            </div>
            <a href="" class="preview-link">
              <h1>How to build robust and dynamic React Native apps</h1>
              <p>Learn about basics of React Native app development</p>
              <span>Read more...</span>
            </a>
          </div>

          <div class="article-preview">
            <div class="article-meta">
              <a href="profile.html"><img src="http://i.imgur.com/N4VcUeJ.jpg" /></a>
              <div class="info">
                <a href="" class="author">Gaza Lang</a>
                <span class="date">March 20th</span>
              </div>
              <button class="btn btn-outline-primary btn-sm pull-xs-right">
                <i class="ion-heart"></i> 10
              </button>
            </div>
            <a href="" class="preview-link">
              <h1>The mark of good web developer.</h1>
              <p>This is the description for the post.</p>
              <span>Read more...</span>
            </a>
          </div>

          <div class="article-preview">
            <div class="article-meta">
              <a href=""><img src="https://i.imgur.com/wAoYezk.jpg"/></a>
              <div class="info">
                <a href="" class="author">Kriss Kawa</a>
                <span class="date">April 25th</span>
              </div>
              <button class="btn btn-outline-primary btn-sm pull-xs-right">
                <i class="ion-heart"></i> 19
              </button>
            </div>
            <a href="" class="preview-link">
              <h1>
                Flutter Image Upload with Firebase
              </h1>
              <p>This is the description for the post.</p>
              <span>Read more...</span>
              <ul class="tag-list">
                <li class="tag-default tag-pill">Flutter</li>
                <li class="tag-default tag-pill">Dart</li>
              </ul>
            </a>
          </div>
        </div>
      </div>
    </div>

Hence, we will get the result as shown in the demo screenshot below:

Finally, we have successfully implemented the articles section of the Home screen with dynamic data using the Vuex mechanism. We also implemented a simple profile screen at the end.

Conclusion

This is the fourth and final chapter of our tutorial series on medium clones using Vue and Vuex. Here, we learned how to fetch the data from the API server and bind it dynamically to the view. We also created a simple Profile screen. Hope this tutorial series was interesting for you. The aim of this tutorial was to provide stepwise guidance on the use of Vue version 3, Vuex, Vue Router, etc.

Until next time folks, Happy coding!

Please Subscribe To Newsletter For Unlock Full Source Code

Loading...

All the code is available on GitHub.

krissanawat

krissanawat

React Native developer from beautiful Chiangmai, love Americano and travel so much