Lifecycle and Data Requests

In this chapter, you will learn Vue3 lifecycle hooks and use fetch to load blog data from a JSON file at the right time.


What is a Lifecycle?

Every Vue component goes through a series of stages from creation to destruction; these stages are called the "lifecycle."

Vue provides correspondinghook functionsat each stage, allowing you to execute code at specific points in time.

For example, if you want to request data after the component finishes loading, you need to useonMountedthis hook.


Vue3 Lifecycle Hooks Overview

Hook FunctionExecution TimingCommon Use Cases
setup()Component initialization, executes the earliestDefine reactive data and computed properties
onBeforeMountBefore the component is mounted to the DOMLess commonly used
onMountedAfter the component is mounted to the DOMMake network requests, initialize third-party libraries, get DOM references
onBeforeUpdateBefore data changes and DOM updatesLess commonly used
onUpdatedAfter data changes and DOM updatesOperations after DOM updates
onBeforeUnmountBefore the component is destroyedClear timers, cancel subscriptions, remove event listeners
onUnmountedAfter the component is destroyedConfirm cleanup work

For the blog project, the one we use most often isonMounted—fetching article data after the page loads.

Lifecycle hooks must be written in<script setup>setup, and cannot be placed inside async functions or conditional statements. Vue determines which component they belong to based on the order of the calls.


Basic Usage of onMounted

Example

<script setup>
import { ref, onMounted } from 'vue'

const message = ref('Loading...')

// onMounted executes after the component is mounted to the page
onMounted(() => {
// At this point, the DOM has finished rendering, so it is safe to manipulate the DOM or send requests.
console.log('Component has mounted, can start loading data now')
message.value = 'Data loading complete!'
})
</script>

<template>
  <p>{{ message }}</p>
</template>

A component can call onMounted multiple times, and they will execute in registration order.


Loading JSON Data with fetch

We will put the article data in a JSON file, then use fetch in onMounted to load it.

Step 1: Create a Data File

Example

// File path: public/posts.json
[
  {
    "id": 1,
"title": "Vue3 Complete Beginner's Guide",
"summary": "fromzerostart学习 Vue3 combinationformula API,涵盖 ref、reactive、computed waitCore Concepts。",
"content": "<h2>is什么学 Vue3?</h2><p>Vue3 Yes目before最flowOf the lineFrontend frameworkOne of...</p>",
    "category": "Vue",
    "date": "2024-05-10"
  },
  {
    "id": 2,
"title": "JavaScript Asynchronous Programming Explained"
"summary": "One article to understand Promise, async/await, event loop, and microtask queue.",
"content": "<h2>What is asynchronous?</h2><p>JS is single-threaded...</p>",
    "category": "JavaScript",
    "date": "2024-05-08"
  },
  {
    "id": 3,
"title": "CSS Grid Layout in Action"
"summary": "Easily implement complex responsive layouts with CSS Grid.",
"content": "<h2>Getting Started with Grid</h2><p>Grid is a two-dimensional layout system...</p>",
    "category": "CSS",
    "date": "2024-05-05"
  }
]

The JSON file is placed in thepublic/directory. Vite will serve it directly as a static file. In the browser, you can access it via/posts.jsondirectly. Files placed in public are not compiled; they are copied as-is to the build output.

Step 2: Fetch Data in the Component

Example

<!-- File path: src/views/HomeView.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'

const articles = ref([]) // initial empty array
const isLoading = ref(true) // loading state
const error = ref(null) // error message

// Asynchronously load article data
async function fetchPosts() {
  isLoading.value = true
  error.value = null
  try {
    const response = await fetch('/posts.json')
// Check whether the response is successful (throws an error on 404 or 500)
    if (!response.ok) {
throw new Error(`Load failed: HTTP ${response.status}`)
    }
    const data = await response.json()
    articles.value = data
  } catch (err) {
    error.value = err.message
console.error('Failed to load article data:', err)
  } finally {
    isLoading.value = false
  }
}

// Load data immediately after the component is mounted
onMounted(() => {
  fetchPosts()
})
</script>

<template>
  <div class="home">
    <h2>Latest Articles</h2>

    <!-- Loading state -->
    <p v-if="isLoading" class="status-msg">Loading, please wait...</p>

    <!-- Error state -->
    <p v-else-if="error" class="status-msg error">
Load failed: {{ error }}
      <button @click="fetchPosts">Retry</button>
    </p>

    <!-- Empty data state -->
    <p v-else-if="articles.length === 0" class="status-msg">
No articles yet, please stay tuned.
    </p>

    <!-- Normal data display -->
    <template v-else>
      <div class="article-grid">
        <BlogCard
         v-for="article in articles"
         :key="article.id"
         :id="article.id"
         :title="article.title"
         :summary="article.summary"
         :date="article.date"
         :category="article.category"
       />
      </div>
    </template>
  </div>
</template>

<style scoped>
.status-msg {
  text-align: center;
  padding: 60px 0;
  font-size: 16px;
  color: #999;
}
.error { color: #e74c3c; }
.error button {
  margin-left: 10px;
  padding: 4px 12px;
  cursor: pointer;
}
</style>

Handling Three States of Async Requests

Every data request should cover the following three UI states, otherwise the user experience will be poor:

StateWhen It AppearsUI Representation
LoadingAfter the request is sent, before the response returnsLoading animation, skeleton screen
SuccessData is returned correctlyRender content normally
FailureNetwork error, server exceptionError message + retry button

You can also handle a fourth state—empty data(the request succeeds but returns an empty array), giving users a clear message instead of a blank page.


Hands-on: Also Switch the Detail Page to fetch Loading

The detail page needs to find the corresponding article from the JSON based on the ID in the URL.

You can reuse the data loading logic—also call fetch on the detail page, then filter.

Example

<!-- File path: src/views/PostView.vue -->
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, RouterLink } from 'vue-router'

const route = useRoute()
const id = Number(route.params.id)

const posts = ref([]) // all posts
const isLoading = ref(true)
const error = ref(null)

// Find current article by ID
const article = computed(() => {
  return posts.value.find(p => p.id === id)
})

// Load all article data
async function fetchPosts() {
  isLoading.value = true
  try {
    const res = await fetch('/posts.json')
if (!res.ok) throw new Error('Failed to load')
    posts.value = await res.json()
  } catch (err) {
    error.value = err.message
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchPosts()
})
</script>

<template>
  <div class="post-view">
    <p v-if="isLoading">Loading...</p>
    <p v-else-if="error">Load failed: {{ error }}</p>
    <div v-else-if="!article">
      <h2>Article does not exist</h2>
      <RouterLink to="/">Back to Home</RouterLink>
    </div>
    <article v-else>
      <span class="category-tag">{{ article.category }}</span>
      <h1>{{ article.title }}</h1>
      <time>{{ article.date }}</time>
      <div class="content" v-html="article.content"></div>
      <RouterLink to="/">← Back to Home</RouterLink>
    </article>
  </div>
</template>

Chapter Summary

In this chapter, you mastered the most essential Vue3 lifecycle hook, onMounted, used fetch to load JSON data from the public directory, and learned how to handle the four UI states of a request (loading/success/failure/empty).

Now the blog data is loaded asynchronously from an external JSON file, which is closer to how real projects work.

Other Extensions