Search code examples
laravelvue.jsasynchronousasync-awaitfetch-api

cannot read properties of null (reading 'value') when using Fetch API


I want to fetch some products from api, the issue is that the template will not wait for the async function to run and for the products prop to be filled with data fetched, as its an async function, so the value of products will be null when the template runs and products.value will give error cannot read properties of null (reading 'value'), so i used v-if to check if there are products before trying to display them, but it displays nothing, as it doesn't catch the change been made to products prop after getting the data, in react with useEffect api i could pass an array of dependencies and sepicify products state to make the update happens, what is the equivalent in vue ? thanks in advance

Route::get('/products',function(){
    $products = DB::table('products')->get()->toArray();
    return response()->json([
        'products' => $products,
    ]);
});

import { ref, watchEffect } from "vue";

const products = ref(null);

watchEffect(() => {
    fetch("/products")
        .then((response) => response.json())
        .then((data) => (products.value = data));
});

<template>
  <div v-if="products">
   <ul v-for="product in products.value">
    <li class="py-4 px-2">
     <Link href="...">
      <img :src="product.imageUrl"/>
       <p>{{ product.name }}</p>
     </Link>
    </li>
   </ul>
  </div>
</template>

Solution

  • Just use <ul v-for="product in products">.

    Refs are unwrapped automatically in templates. So initially you are trying to get (null).value.

    Also you don't need watchEffect() (there's no reactive dependencies inside it), just do the fetch in the component's root.

    I also suggest to avoid using refs for non-primitives. That way you can avoid unwrapping refs in the script secion.

    You can also omit <div v-if="products"> since initially products is an empty array:

    import { reactive, onMounted } from "vue";
    
    const products = reactive([]);
    
    onMounted(async () => products.push(...await(await fetch("/products")).json()));
    
    <template>
       <ul v-for="product in products">
        <li class="py-4 px-2">
         <Link href="...">
          <img :src="product.imageUrl"/>
           <p>{{ product.name }}</p>
         </Link>
        </li>
       </ul>
    </template>