benjaminadon
benjaminadon

Reputation: 153

Why am I getting property undefined with my vue.js component?

I have a few components in my vue.js app that follow the structure below and they are all throwing the same error.

TypeError: Cannot read property 'id' of undefined

I thought this would be an easy fix by wrapping {{ jobs[0].id }} with a <template :v-if="jobs"> or <template :v-if="jobs[0].id">, as most other stackoverflow posts point to this as a solution, but this does make the errors go away. What's interesting is with the code below, all the data (including id) renders to the page just fine, there is no data missing from the page, however the console.log continues to show the property undefined error and my unittests fail with the same error.

<template>
  <div class="emptyDiv">
    <h3>
      Latest Build -
      <router-link:to="{name: 'job_details'}">
      {{ jobs[0].id }}
      </router-link>
    </h3>
  </div>
<template>

<script>
export default {
  name: "Stages",
  data() {
    return {
      jobs: []
    };
  },
  created() {
    this.JobExecEndpoint =
    process.env.VUE_APP_TEST_URL +
    "/api/v2/job/"
    fetch(this.JobExecEndpoint)
    .then(response => response.json())
    .then(body => {
      this.cleanStartTime = moment(body[0].time_start);
      this.cleanEndTime = moment(body[0].time_end);
      this.cleanDuration = this.calculateDuration(
        this.cleanStartTime,
        this.cleanEndTime
        );
      this.jobs.push({
        name: body[0].job.name,
        id: body[0].id,
        env: body[0].job.env,
        time_start: this.cleanStartTime.format("LLL"),
        time_end: this.cleanEndTime.format("LLL"),
        duration: this.cleanDuration,
        status: body[0].status.name,
        job: body[0].job,
      });
    })
    .catch(err => {
      console.log("Error Fetching:", this.JobExecEndpoint, err);
      return { failure: this.JobExecEndpoint, reason: err };
    });
  }
};
</script>

What am I doing wrong here? I have tried to troubleshoot these errors for about 10 hours straight and have not been able to figure it out.

UPDATE:

I was able to get rid of these errors and pass my unittests by adding

  .then(() => {
  this.new_id = this.jobs[0].id
  })

to the created block and adding new_id='' under data

and then calling new_id from the template. However, this is not a satisfactory solution as I still don't know why it's throwing the error with the original code, and I don't believe this solution will work with a a full array (with more than one job).

Upvotes: 1

Views: 2807

Answers (1)

skirtle
skirtle

Reputation: 29092

You mentioned that you've tried <template :v-if="jobs"> and <template :v-if="jobs[0].id"> but neither of these addresses the problem here. Firstly, the colon before v-if is wrong, I assume that's just a typo in the question. Secondly, neither of those conditions is actually targeting the undefined value in question.

First let's consider this:

<template v-if="jobs">

An empty array counts as truthy, so this check will always pass.

Then this:

<template v-if="jobs[0].id">

This has exactly the same problem as the original code, it's trying to access the id property of the first element and the first element is undefined. You can't access a property of undefined.

What you need is to check that the first element exists, so something like this:

<template v-if="jobs[0]">

Or:

<template v-if="jobs.length">

Or rewrite {{ jobs[0].id }} as:

{{ jobs[0] && jobs[0].id }}

The reason it appears to render correctly is that the page will re-render after the data is returned from the server. This second rendering will complete successfully as jobs is now populated. It's the initial render that fails, before the data is returned.

Upvotes: 2

Related Questions