Launch the next Yelp in minutes Download this gorgeous React Native Store Locator app template…
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.vue, Login.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';
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> New Post
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">
<i class="ion-gear-a"></i> 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> New Post
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">
<i class="ion-gear-a"></i> 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!
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:
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> 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 baseURL
option 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
, password
and 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!
All the code is available on GitHub.
There is no ads to display, Please add some