Vue 3 Component Props API
March 7, 2025 Β· AI Conversations
### Basic Usage of `defineProps`
<script setup>
defineProps({
title: String,
count: Number,
isActive: Boolean
});
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<p v-if="isActive">This is active</p>
</div>
</template>
—
### Using Props with Default Values
Vue 3 doesnβt allow setting default values directly inside `defineProps`. Instead, you can use `withDefaults`:
<script setup>
const props = withDefaults(defineProps<{
title?: string;
count?: number;
}>(), {
title: "Default Title",
count: 0
});
</script>
<template>
<div>
<h1>{{ props.title }}</h1>
<p>Count: {{ props.count }}</p>
</div>
</template>
—
### Props with TypeScript and Validation
If you're using TypeScript, you can define props with specific types:
<script setup lang="ts">
defineProps<{
title: string;
count?: number;
isActive: boolean;
}>();
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count ?? 'Not provided' }}</p>
<p v-if="isActive">This is active</p>
</div>
</template>
—
### Example of Passing Props from a Parent Component
**Parent Component:**
<ChildComponent title="Hello Vue 3" :count="5" :isActive="true" />
**Child Component (`ChildComponent.vue`):**
<script setup>
defineProps({
title: String,
count: Number,
isActive: Boolean
});
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<p v-if="isActive">Active</p>
</div>
</template>
This is how you handle props in Vue 3's Composition API. Let me know if you need more details! π
—
### **Child Component (`NewsComponent.vue`)**
<script setup>
defineProps({
newsApiUrl: String
});
</script>
<template>
<div>
<p>API URL: {{ newsApiUrl }}</p>
</div>
</template>
—
### **Passing the Prop from Parent Component**
<template>
<NewsComponent newsApiUrl="https://api.example.com/news" />
</template>
<script setup>
import NewsComponent from "./NewsComponent.vue";
</script>
—
### **Using `withDefaults` to Set a Default Value**
If you want to provide a default value for `newsApiUrl` in case the parent doesnβt pass it:
<script setup>
const props = withDefaults(defineProps<{
newsApiUrl?: string;
}>(), {
newsApiUrl: "https://api.default.com/news"
});
</script>
<template>
<div>
<p>API URL: {{ props.newsApiUrl }}</p>
</div>
</template>
—
Would you like to fetch data using `newsApiUrl` inside the component? π
—
### **Child Component (`NewsComponent.vue`)**
<script setup>
import { defineProps, watchEffect } from "vue";
const props = defineProps({
newsApiUrl: String
});
// React to changes in `newsApiUrl`
watchEffect(() => {
if (props.newsApiUrl) {
console.log("News API URL changed:", props.newsApiUrl);
fetchNews(props.newsApiUrl);
}
});
// Function to fetch news data (example)
const fetchNews = (url) => {
console.log(`Fetching news from: ${url}`);
// Here you can add your fetch logic
};
</script>
<template>
<div>
<p>API URL: {{ newsApiUrl }}</p>
</div>
</template>
—
### **Parent Component Example**
This parent component dynamically updates the `newsApiUrl` prop.
<template>
<div>
<button @click="changeApiUrl">Change API URL</button>
<NewsComponent :newsApiUrl="apiUrl" />
</div>
</template>
<script setup>
import { ref } from "vue";
import NewsComponent from "./NewsComponent.vue";
const apiUrl = ref("https://api.example.com/news");
const changeApiUrl = () => {
apiUrl.value = "https://api.newsource.com/latest-news";
};
</script>
—
### **How `watchEffect` Works Here**
– When `newsApiUrl` changes (e.g., when `changeApiUrl` is triggered), `watchEffect` automatically runs.
– The new URL is logged, and `fetchNews` is called with the updated URL.
Would you like to add error handling or loading state to the fetch request? π
<script>
import { ref, onMounted,watchEffect } from 'vue';
import NewsComponent from './NewsComponent.vue';
import axios from "axios";
const props = defineProps(['apiurl'])
console.log(props.apiurl)
const posts = ref([]);
const loading = ref(true);
const error = ref(null);
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
watchEffect(() => {
if (props.apiurl) {
console.log("News API URL changed:", props.apiurl);
fetchPosts(props.apiurl);
}
});
const fetchPosts = async (url) => {
try {
const response = await axios.get(`${url}`);
posts.value = response.data;
console.log(posts.value);
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
onMounted(fetchPosts);
</script>
Loading…
{{ error }}
{{ post.title.rendered }}
<style>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>
can not access fetchposts before initialization
### **Solution:** Move `fetchPosts` Above Its Usage
<script setup>
import { ref, onMounted, watchEffect } from "vue";
import axios from "axios";
const props = defineProps(["apiurl"]);
const posts = ref([]);
const loading = ref(true);
const error = ref(null);
const count = ref(0);
// Function that fetches posts
const fetchPosts = async (url) => {
try {
loading.value = true; // Reset loading state
const response = await axios.get(url);
posts.value = response.data;
console.log(posts.value);
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
// Watch for changes in `apiurl`
watchEffect(() => {
if (props.apiurl) {
console.log("News API URL changed:", props.apiurl);
fetchPosts(props.apiurl);
}
});
// Lifecycle hook
onMounted(() => {
console.log(`The initial count is ${count.value}.`);
if (props.apiurl) {
fetchPosts(props.apiurl); // Initial fetch on mount
}
});
</script>
<template>
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<div v-if="!loading && !error">
<div class="item" v-for="post in posts" :key="post.id">
<div class="details">
<a :href="post.link" target="_blank">
<h3>{{ post.title.rendered }}</h3>
</a>
<p v-html="post.excerpt.rendered"></p>
</div>
</div>
</div>
</template>
### **Key Fixes & Improvements:**
1. **Moved `fetchPosts` Above Its First Usage**
– This prevents the "Cannot access before initialization" error.
2. **Ensured `fetchPosts` Runs on Initial Mount**
– Calls `fetchPosts(props.apiurl)` inside `onMounted()` if `apiurl` exists.
3. **Handles `loading` Properly**
– Resets `loading` when fetching new data.
4. **Error Handling**
– Shows error message if the API request fails.
Let me know if you need further improvements! π
PostMymeds
ConnactTribune
change url from above and change listing
—
### **Updated Parent Component (Handles API URL Change)**
<template>
<div>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="https://postmymeds.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="one">PostMymeds</label>
<input type="radio" id="two" value="https://connacttribune.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="two">ConnactTribune</label>
<!-- Pass selected API URL to NewsComponent -->
<NewsComponent :apiurl="picked" />
</div>
</template>
<script setup>
import { ref } from "vue";
import NewsComponent from "./NewsComponent.vue";
// Default API URL
const picked = ref("https://postmymeds.co.uk/wp-json/wp/v2/posts");
</script>
—
### **Child Component (`NewsComponent.vue` – Fetches and Displays Posts)**
<script setup>
import { ref, watchEffect } from "vue";
import axios from "axios";
const props = defineProps(["apiurl"]);
const posts = ref([]);
const loading = ref(true);
const error = ref(null);
// Function to fetch posts
const fetchPosts = async (url) => {
try {
loading.value = true;
error.value = null;
const response = await axios.get(url);
posts.value = response.data;
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
// Watch for changes in `apiurl` and fetch new data
watchEffect(() => {
if (props.apiurl) {
console.log("Fetching from:", props.apiurl);
fetchPosts(props.apiurl);
}
});
</script>
<template>
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<div v-if="!loading && !error">
<div class="item" v-for="post in posts" :key="post.id">
<div class="details">
<a :href="post.link" target="_blank">
<h3>{{ post.title.rendered }}</h3>
</a>
<p v-html="post.excerpt.rendered"></p>
</div>
</div>
</div>
</template>
—
### **How It Works:**
1. **Parent Component:**
– Has two radio buttons that allow switching between API URLs.
– Passes the selected URL as a prop to `NewsComponent`.
2. **Child Component (`NewsComponent.vue`):**
– Uses `watchEffect` to reactively fetch data when `apiurl` changes.
– Displays a loading message while fetching.
– Shows an error message if fetching fails.
### **What Happens When You Select a Different Radio Button?**
β
The `apiurl` prop in `NewsComponent` updates.
β
`watchEffect` detects the change and triggers `fetchPosts`.
β
New posts from the selected API are displayed.
—
π Now, changing the radio button will dynamically update the post listing! Let me know if you need further enhancements. π
<script>
import { ref, onMounted,watchEffect } from 'vue';
import NewsComponent from './NewsComponent.vue';
import axios from "axios";
const props = defineProps(['apiurl'])
console.log(props.apiurl)
const posts = ref([]);
const loading = ref(true);
const error = ref(null);
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
const fetchPosts = async (url) => {
try {
const response = await axios.get(`${url}`);
posts.value = response.data;
console.log(posts.value);
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
// Watch for changes in `apiurl`
watchEffect(() => {
if (props.apiurl) {
console.log("News API URL changed:", props.apiurl);
fetchPosts(props.apiurl);
}
});
// Lifecycle hook
onMounted(() => {
console.log(`The initial count is ${count.value}.`);
if (props.apiurl) {
fetchPosts(props.apiurl); // Initial fetch on mount
}
});
</script>
Picked: {{ picked }}
PostMymeds
ConnactTribune
Loading…
{{ error }}
{{ post.title.rendered }}
<style>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>
change in this component self
However, if you want to keep everything in one component, you need to:
β
Add `picked` as a reactive variable.
β
Update `apiurl` based on `picked`.
β
Ensure posts update when `picked` changes.
—
### **Updated Component**
<script setup>
import { ref, watchEffect } from "vue";
import axios from "axios";
// Reactive state
const picked = ref("https://postmymeds.co.uk/wp-json/wp/v2/posts"); // Default selection
const posts = ref([]);
const loading = ref(false);
const error = ref(null);
// Function to fetch posts
const fetchPosts = async (url) => {
loading.value = true;
error.value = null;
try {
const response = await axios.get(url);
posts.value = response.data;
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
// Watch for changes in `picked` and fetch new posts
watchEffect(() => {
if (picked.value) {
console.log("Fetching from:", picked.value);
fetchPosts(picked.value);
}
});
</script>
<template>
<div>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="https://postmymeds.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="one">PostMymeds</label>
<input type="radio" id="two" value="https://connacttribune.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="two">ConnactTribune</label>
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<div v-if="!loading && !error">
<div class="item" v-for="post in posts" :key="post.id">
<div class="details">
<a :href="post.link" target="_blank">
<h3>{{ post.title.rendered }}</h3>
</a>
<p v-html="post.excerpt.rendered"></p>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
}
</style>
—
### **Key Changes**
β
Removed unnecessary props (`apiurl`).
β
Used `watchEffect` to refetch posts when `picked` changes.
β
Optimized error handling.
π Now, changing the radio button updates the post list dynamically!
### **Why is this happening?**
1. `watchEffect` can re-run multiple times if there are multiple reactive dependencies.
2. Vue's reactivity might be tracking extra state changes, causing multiple re-executions.
3. The initial fetch (`onMounted`) and `watchEffect` may both be triggering API calls.
—
### **Solution: Use `watch` Instead of `watchEffect`**
`watchEffect` tracks everything inside it, while `watch` only tracks specific changes. Let's update your component to ensure the API is only called once per change.
—
### **Updated Component with Optimized API Calls**
“`vue
<script>
import { ref, watch } from "vue";
import axios from "axios";
// Reactive state
const picked = ref("https://postmymeds.co.uk/wp-json/wp/v2/posts"); // Default selection
const posts = ref([]);
const loading = ref(false);
const error = ref(null);
// Function to fetch posts
const fetchPosts = async (url) => {
loading.value = true;
error.value = null;
try {
console.log("Fetching posts from:", url);
const response = await axios.get(url);
posts.value = response.data;
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false;
}
};
// Watch for changes in `picked` and fetch posts (Runs only once per change)
watch(picked, (newUrl) => {
if (newUrl) {
fetchPosts(newUrl);
}
}, { immediate: true }); // Runs on component mount as well
</script>
Picked: {{ picked }}
PostMymeds
ConnactTribune
### **Updated Code with Immediate Loading on Toggle**
<script setup>
import { ref, watch } from "vue";
import axios from "axios";
// Reactive state
const picked = ref("https://postmymeds.co.uk/wp-json/wp/v2/posts"); // Default selection
const posts = ref([]);
const loading = ref(false);
const error = ref(null);
// Function to fetch posts
const fetchPosts = async (url) => {
loading.value = true; // Set loading before fetching
error.value = null;
try {
console.log("Fetching posts from:", url);
const response = await axios.get(url);
posts.value = response.data;
} catch (err) {
error.value = "Error fetching posts";
} finally {
loading.value = false; // Turn off loading after response
}
};
// Watch for changes in `picked` and fetch posts
watch(picked, (newUrl) => {
if (newUrl) {
loading.value = true; // Show loading immediately when URL changes
fetchPosts(newUrl);
}
}, { immediate: true }); // Runs on component mount as well
</script>
<template>
<div>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="https://postmymeds.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="one">PostMymeds</label>
<input type="radio" id="two" value="https://connacttribune.co.uk/wp-json/wp/v2/posts" v-model="picked" />
<label for="two">ConnactTribune</label>
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<div v-if="!loading && !error">
<div class="item" v-for="post in posts" :key="post.id">
<div class="details">
<a :href="post.link" target="_blank">
<h3>{{ post.title.rendered }}</h3>
</a>
<p v-html="post.excerpt.rendered"></p>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
</style>
### **Key Fixes:**
β
**`loading.value = true` is set immediately** when `picked` changes, ensuring the "Loading…" message appears.
β
**Optimized watch function** to prevent multiple API calls.
β
**Immediate API call on mount (`immediate: true`)** for the initial state.
Now, whenever the user toggles the radio button, the loading indicator appears instantly until the API response is received. π
Both `watch` and `watchEffect` are used to reactively track changes in Vue, but they have key differences in behavior and use cases.
—
## **1. `watch` – Explicit Dependency Tracking**
– **Use case**: When you want to watch a specific reactive variable and execute a function when it changes.
– **Requires specifying dependencies explicitly**.
– **Provides access to old and new values**.
– **Useful for async operations like API calls**.
### **Example (`watch`)**
<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
// Watching a specific variable
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});
const increment = () => {
count.value++;
};
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
β
**Key Benefit**: Only runs when `count` changes.
β
**Has access to old and new values**.
β
**Efficient, as it doesnβt run unnecessarily**.
—
## **2. `watchEffect` – Automatic Dependency Tracking**
– **Use case**: When you want a function to run whenever any **reactive dependencies inside it** change.
– **Tracks dependencies automatically** without needing to specify them.
– **Runs immediately when the component is mounted**.
– **Good for operations that depend on multiple reactive values**.
### **Example (`watchEffect`)**
<script setup>
import { ref, watchEffect } from 'vue';
const count = ref(0);
const double = ref(0);
// Automatically tracks all used dependencies
watchEffect(() => {
double.value = count.value * 2;
console.log(`Count: ${count.value}, Double: ${double.value}`);
});
const increment = () => {
count.value++;
};
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<button @click="increment">Increment</button>
</div>
</template>
β
**Key Benefit**: Automatically detects changes in `count` and updates `double`.
β
**No need to explicitly specify dependencies**.
β
**Runs once immediately upon mounting**.
—
## **π Key Differences**
| Feature | `watch` | `watchEffect` |
|—————–|——–|————–|
| **Explicit dependency tracking** | β
Required (needs to specify variable) | β Not needed (auto-detects dependencies) |
| **Access to old and new values** | β
Yes | β No |
| **Runs immediately on mount** | β No (unless `immediate: true`) | β
Yes |
| **Handles multiple dependencies** | β
But must be explicitly defined | β
Automatically tracks all used dependencies |
| **Best for** | Watching specific variables, API calls | Auto-tracking dependencies inside a function |
—
## **πΉ When to Use What?**
β
Use `watch` when:
– You need to track a **specific variable**.
– You need **old and new values** (e.g., logging changes).
– You need **to debounce API calls** when a value changes.
β
Use `watchEffect` when:
– You want **automatic tracking** of dependencies.
– You need **side effects** that depend on multiple reactive values.
– You need **code to run immediately** when the component is mounted.
—
## **Final Thoughts**
– Use `watchEffect` for **side effects** that rely on **multiple reactive values** and should run immediately.
– Use `watch` when you need **explicit tracking**, access to **old/new values**, or **asynchronous logic**.
Would you like an example based on your project? π