vue.jsvuejs3

Fetch data from API to inject data into Vue ref and use in html template


I'm new to the front end and just trying things out. I followed the tutorial and vue examples to create a simple web app. I want to populate the dropdown select box with data fetched from the backend now. But whatever I tried it just didn't work and fails with TheSandbox.vue:18 Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'sendOptions'). Unfortunately, all the vue examples in the tutorial are for the static data set. What am I missing here? The code is below:

App.vue

<script setup>
import TheHeader from './components/TheHeader.vue'
import TheSandbox from './components/TheSandbox.vue';
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <TheHeader msg="You did it!" />
    </div>
  </header>

  <main>
    <TheSandbox />
  </main>
</template>

TheSandbox.vue

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

const sendAmount = ref('')
const sendSelected = ref('')
const sendOptions = ref([])

async function fetchItems() {
    return fetch('<MYURL>', {
        method: 'GET',
        headers: {
            "Content-Type": "application/json",
        },
    }).then(response => {
        console.log(response)

        response.json().then(data => {
            this.sendOptions = data.map(item => ({
                value: item.code, // actual value for the option
                text: item.name, // display text for the option
            }))

            console.log(data)
        });
    }).catch(err => {
        console.error(err);
    });
}

import { onMounted } from 'vue'
import { nextTick } from 'vue'

onMounted(async () => {
    // Fetch the data first
    await fetchItems();

    // Use await nextTick to wait until the DOM is updated
    await nextTick();

    // Now, you can interact with the DOM if necessary (e.g., selecting a default option)
    console.log('DOM updated and options are rendered');
})
</script>

<template>
    <div class="item">
        <div class="details">
            <h3>
                You send {{ sendAmount }} {{ sendSelected }}
            </h3>
            <input v-model="sendAmount" placeholder="0" />
            <select v-model="sendSelected">
                <option v-for="option in sendOptions" :key="option.value" :value="option.value">
                    {{ option.text }}
                </option>
            </select>
        </div>
    </div>
</template>

Thanks!


Solution

  • refer to sendOptions with sendOptions.value since it's declared with ref:

    sendOptions.value = data.map(item => ({
            value: item.code, // actual value for the option
             text: item.name, // display text for the option
          }))
    
    

    this refers to the global scope (window) which doesn't have the property sendOptions.

    Try to restructure your code in this way :

    <script setup>
    import { ref, onMounted, nextTick } from "vue";
    
    const sendAmount = ref("");
    const sendSelected = ref("");
    const sendOptions = ref([]);
    
    async function fetchItems() {
      try {
        const response = await fetch("<MYURL>", {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        });
    
        const data = await response.json();
        sendOptions.value = data.map((item) => ({
          value: item.code, // actual value for the option
          text: item.name, // display text for the option
        }));
    
        console.log(data);
      } catch (err) {
        console.error(err);
      }
    }
    
    onMounted(async () => {
      // Fetch the data first
      await fetchItems();
    
      // Use await nextTick to wait until the DOM is updated
      await nextTick();
    
      // Now, you can interact with the DOM if necessary (e.g., selecting a default option)
      console.log("DOM updated and options are rendered");
    });
    </script>