Vue 3 Component Props API

User
vue 3 component props composiiton api
Assistant
In Vue 3 Composition API, you can define props inside the `setup` function using the `defineProps` function. Here's how you can use props in a Vue 3 component:

### 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! πŸš€

User
if props name is newsApiUrl
Assistant
If your prop name is `newsApiUrl`, you can define and use it in a Vue 3 component with the Composition API as follows:

### **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? πŸš€

User
use watchEffect to get value change in props
Assistant
If you want to watch for changes in the `newsApiUrl` prop and react accordingly using `watchEffect`, you can do it like this:

### **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? πŸš€

User
<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

Assistant
The error **"Cannot access 'fetchPosts' before initialization"** occurs because you're calling `onMounted(fetchPosts);`, but `fetchPosts` is defined later in the script. JavaScript follows a **hoisting** mechanism, and `const fetchPosts = async (url) => {}` is not hoisted like function declarations.

### **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! πŸš€

User
Picked: {{ picked }}

PostMymeds

ConnactTribune
change url from above and change listing

Assistant
To dynamically change the `apiurl` when a radio button is selected and update the post listing accordingly, follow this approach:

### **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. 😊

User
<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

Assistant
You're mixing concerns in this componentβ€”you're both handling the radio selection (`picked`) and fetching posts based on `apiurl`. Ideally, the radio selection should be in the parent component, and this component should only handle fetching posts.

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!

User
also so laoding 3 when radio changes
Assistant
It looks like your API is being called three times when the radio button changes. This usually happens due to **multiple reactivity triggers** in Vue, especially with `watchEffect`.

### **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

User
show loading true when toggle changed
Assistant
To ensure `loading` is set to `true` immediately when the radio button changes, update the `watch` function to trigger `loading.value = true` before calling the API.

### **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. πŸš€

User
what is the diff between watch and watch effect
Assistant
### **Difference Between `watch` and `watchEffect` in Vue 3**

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? πŸš€

Leave a Comment

Your email address will not be published. Required fields are marked *