Bird
Raised Fist0
Vueframework~8 mins

Loading states pattern in Vue - Performance & Optimization

Choose your learning style10 modes available
Performance: Loading states pattern
MEDIUM IMPACT
This pattern affects how quickly users see feedback during data fetching, impacting perceived load speed and interaction responsiveness.
Displaying a loading indicator while fetching data asynchronously
Vue
<template>
  <div :aria-busy="loading" aria-live="polite">
    <div v-if="loading" class="simple-loader" role="status" aria-label="Loading content"></div>
    <DataDisplay v-else :data="data" />
  </div>
</template>

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

const loading = ref(true)
const data = ref(null)

async function fetchData() {
  // simulate data fetching
  return new Promise(resolve => setTimeout(() => resolve({}), 1000))
}

onMounted(async () => {
  data.value = await fetchData()
  loading.value = false
})
</script>

<style scoped>
.simple-loader {
  width: 2rem;
  height: 2rem;
  border: 0.25rem solid #ccc;
  border-top-color: #333;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
</style>
Using a simple CSS loader reduces bundle size and rendering cost, enabling faster first paint and smoother interaction.
📈 Performance GainSingle reflow, non-blocking rendering, saves ~50kb in bundle size compared to heavy spinner component.
Displaying a loading indicator while fetching data asynchronously
Vue
<template>
  <div v-if="loading">
    <HeavySpinnerComponent />
  </div>
  <div v-else>
    <DataDisplay :data="data" />
  </div>
</template>

<script setup>
import HeavySpinnerComponent from './HeavySpinnerComponent.vue'
import DataDisplay from './DataDisplay.vue'
import { ref, onMounted } from 'vue'

const loading = ref(true)
const data = ref(null)

async function fetchData() {
  // simulate data fetching
  return new Promise(resolve => setTimeout(() => resolve({}), 1000))
}

onMounted(async () => {
  data.value = await fetchData()
  loading.value = false
})
</script>
Using a heavy spinner component blocks initial rendering and increases bundle size, delaying first paint and interaction readiness.
📉 Performance CostBlocks rendering for 200ms+ due to large spinner component and triggers multiple reflows when toggling loading state.
Performance Comparison
PatternDOM OperationsReflowsPaint CostVerdict
Heavy spinner componentHigh (many nodes)Multiple reflows on toggleHigh paint cost due to SVG and complex styles[X] Bad
Simple CSS loaderLow (few nodes)Single reflowLow paint cost with simple animation[OK] Good
Rendering Pipeline
Loading states trigger conditional rendering which affects style calculation and layout. Heavy components increase paint and composite times, while simple loaders minimize these costs.
→Style Calculation
→Layout
→Paint
→Composite
⚠️ BottleneckPaint and Composite stages due to complex spinner SVG or heavy component rendering.
Core Web Vital Affected
INP
This pattern affects how quickly users see feedback during data fetching, impacting perceived load speed and interaction responsiveness.
Optimization Tips
1Use simple CSS animations for loading indicators to minimize paint cost.
2Avoid heavy spinner components that increase bundle size and reflows.
3Always provide accessible loading feedback to improve perceived responsiveness.
Performance Quiz - 3 Questions
Test your performance knowledge
Which loading state pattern improves interaction responsiveness the most?
ANot showing any loading indicator until data loads
BUsing a heavy spinner component with SVG and many elements
CUsing a simple CSS spinner with minimal DOM nodes
DShowing a static image as a loading indicator
DevTools: Performance
How to check: Record a performance profile while toggling loading state. Look for long paint or composite tasks and multiple reflows.
What to look for: Check for long frames caused by heavy spinner rendering and verify minimal layout shifts with simple loaders.

Practice

(1/5)
1. What is the main purpose of using a loading state in a Vue component?
easy
A. To disable Vue reactivity
B. To show users that data is being fetched or processed
C. To permanently hide content from users
D. To increase the component's size

Solution

  1. Step 1: Understand user experience during data fetching

    Loading states inform users that the app is working on a task, preventing confusion.
  2. Step 2: Identify the purpose of loading states in Vue

    They show progress or waiting messages while data loads or actions complete.
  3. Final Answer:

    To show users that data is being fetched or processed -> Option B
  4. Quick Check:

    Loading states = user feedback [OK]
Hint: Loading states show progress to users during waits [OK]
Common Mistakes:
  • Thinking loading states hide content permanently
  • Confusing loading states with disabling reactivity
  • Assuming loading states change component size
2. Which Vue syntax correctly shows a loading message only when isLoading is true?
easy
A. <div v-show="isLoading">Loading...</div>
B. <div v-if="!isLoading">Loading...</div>
C. <div v-if="isLoading">Loading...</div>
D. <div v-bind:isLoading>Loading...</div>

Solution

  1. Step 1: Understand v-if usage

    v-if conditionally renders the element only if the expression is true.
  2. Step 2: Match condition to show loading

    We want to show the message when isLoading is true, so v-if="isLoading" is correct.
  3. Final Answer:

    <div v-if="isLoading">Loading...</div> -> Option C
  4. Quick Check:

    Show loading if isLoading true = v-if="isLoading" [OK]
Hint: Use v-if with the loading variable directly [OK]
Common Mistakes:
  • Using v-if with negation (!isLoading) to show loading
  • Using v-show which toggles visibility but keeps element
  • Using invalid v-bind syntax for conditional rendering
3. Given this Vue setup:
const isLoading = ref(true);
setTimeout(() => { isLoading.value = false; }, 2000);
What will the component show initially if template has <div v-if="isLoading">Loading...</div><div v-else>Content Loaded</div>?
medium
A. Loading... for 2 seconds, then Content Loaded
B. Content Loaded immediately
C. Nothing is shown
D. Loading... forever

Solution

  1. Step 1: Analyze initial value of isLoading

    It starts as true, so v-if="isLoading" shows "Loading..." initially.
  2. Step 2: Understand the timeout effect

    After 2 seconds, isLoading.value becomes false, switching to v-else content "Content Loaded".
  3. Final Answer:

    Loading... for 2 seconds, then Content Loaded -> Option A
  4. Quick Check:

    Initial true shows loading, then false shows content [OK]
Hint: Check initial state and delayed change for output [OK]
Common Mistakes:
  • Assuming content shows immediately ignoring initial true
  • Thinking loading never stops without reactivity knowledge
  • Confusing v-if and v-else rendering timing
4. Identify the error in this Vue code snippet for loading state:
const isLoading = ref(false);

function fetchData() {
  isLoading = true;
  fetch('/api/data')
    .then(() => { isLoading = false; });
}
medium
A. No error, code works fine
B. Missing async keyword on fetchData function
C. fetch API call is incorrect syntax
D. Directly assigning to ref variable instead of its .value

Solution

  1. Step 1: Understand how to update Vue refs

    Refs are objects; their value must be changed via .value, not by direct assignment.
  2. Step 2: Identify incorrect assignments

    Code assigns isLoading = true instead of isLoading.value = true, causing errors.
  3. Final Answer:

    Directly assigning to ref variable instead of its .value -> Option D
  4. Quick Check:

    Update ref with .value, not direct assignment [OK]
Hint: Change ref values with .value property [OK]
Common Mistakes:
  • Assigning ref variable directly instead of .value
  • Forgetting to handle promise with then or async/await
  • Assuming fetch syntax is wrong when it is correct
5. You want to disable a submit button during loading in Vue. Which code snippet correctly disables the button and shows loading text using isLoading reactive variable?
hard
A. <button :disabled="isLoading">{{ isLoading ? 'Loading...' : 'Submit' }}</button>
B. <button v-if="isLoading" disabled>Submit</button><button v-else>Loading...</button>
C. <button :disabled="!isLoading">Submit</button>
D. <button :loading="isLoading">Submit</button>

Solution

  1. Step 1: Disable button based on loading state

    Use :disabled="isLoading" to disable the button when loading.
  2. Step 2: Show dynamic button text

    Use interpolation with a ternary to show "Loading..." or "Submit" based on isLoading.
  3. Final Answer:

    <button :disabled="isLoading">{{ isLoading ? 'Loading...' : 'Submit' }}</button> -> Option A
  4. Quick Check:

    Disable button and show loading text with :disabled and ternary [OK]
Hint: Bind :disabled and use ternary text inside button [OK]
Common Mistakes:
  • Using v-if to swap buttons instead of disabling
  • Negating isLoading for disabling logic
  • Using non-existent :loading attribute on button
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard