I have a site that shows job listings, so it has a /pages/jobs/index.vue and a /pages/jobs/_slug.vue to show a main jobs page and a child page for each job.
The child and parent page use the Contentful delivery API to fetch data about the jobs.
If I navigate to a child page that doesn't exist e.g. /jobs/blahblah then I've added an error message and cta that links back to the main jobs page. However, when I do this and click the cta to go back to /jobs the jobs page fails to fetch the Contentful data and shows an empty page.
If I navigate to a job child page that does exist, then navigate to the main jobs page, everything is fine.
I saw other people suggest using a watch function, that watches for url changes and refetches the data. So I tried this (see /pages/jobs/index.vue), using the 2 ways I could find online but it didn't help:
watch: {
'this.$route.query': '$fetch'
},
watchQuery: true,
/pages/jobs/_slug.vue:
<template>
<div>
<Subpage
v-if="!$fetchState.pending"
:navLinks="navLinks"
:heroData="heroData"
:childPage="true"
/>
<div v-if="$fetchState.error">
<p>Sorry, this job is no longer available</p>
<CallToAction url="/jobs" label="Check out our other jobs" />
</div>
<div v-else>
<div class="job-details-container">
<h2>Job Outline</h2>
<div
class="job-outline"
v-html="this.jobOutline"
></div>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue";
import { fetchEntriesByContentType } from "../../../services/contentful-service";
import Subpage from "../../../components/header/Subpage.vue";
import CallToAction from "../../../components/CallToAction.vue";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
export default {
name: "job-details",
components: { Subpage, CallToAction },
data() {
return {
slug: '',
pageData: {},
seoMetaData: {},
navLinks: [],
};
},
async fetch() {
try {
const path = this.$route.path;
const slug = path.split("/")[path.split("/").length - 1];
const [job] = await fetchEntriesByContentType({
contentType: "jobsCard",
slug,
});
const [navbar] = await fetchEntriesByContentType({
contentType: "navbar"
});
if (!job || !navbar) {
throw `Error-fetching jobs child page data`;
} else {
this.pageData = job?.fields;
this.seoMetaData = job?.fields?.jobDetail
this.navLinks = navbar;
this.slug = slug;
}
} catch (e) {
if (process.env.ROLLBAR_ENABLED === true) {
Vue.rollbar.error(e);
}
throw new Error(e);
}
},
computed: {
heroData() {
return {
pageTitle: this.pageData?.jobTitle,
heroImageUrl: this.pageData?.jobDetail.image.url
};
},
jobOutline() {
return documentToHtmlString(this.pageData?.jobDetail?.fields?.jobOutline);
},
},
};
</script>
/pages/jobs/index.vue:
<template>
<div>
<Subpage
v-if="!$fetchState.pending"
:navLinks="navLinks"
:heroData="heroData"
/>
<div>
<ul>
<JobCard
v-for="card in jobCards"
:key="getCardTitle(card)"
:jobData="{ ...card.fields, itemId: card.sys.id }"
/>
</ul>
</div>
</div>
</template>
<script>
import Vue from "vue";
import Subpage from "../../../components/header/Subpage.vue";
import JobCard from "../../../components/jobs/JobCard.vue";
import { fetchEntriesByContentType } from "../../../services/contentful-service";
export default {
name: "jobs-page",
components: { Subpage, JobCard },
data() {
return {
headerData: {},
seoMetaData: {},
jobCards: [],
};
},
watch: {
'this.$route.query': '$fetch'
},
watchQuery: true,
async fetch() {
try {
const data = await fetchEntriesByContentType({
contentType: "pageSection",
title: "Jobs",
});
if (!data || data.length < 1) {
throw `Error- fetching jobs page data`;
} else {
this.headerData = data[0].fields?.header;
this.seoMetaData = data[0].fields?.seoMetadata?.fields || {};
this.jobCards = data[0].fields?.bodySection?.fields?.jobCard;
}
} catch (error) {
if (process.env.ROLLBAR_ENABLED === true) {
Vue.rollbar.error(error);
}
throw new Error(error);
}
},
methods: {
getCardTitle(card) {
return card?.fields?.title;
},
},
computed: {
navLinks() {
return this.headerData?.fields?.navBar;
},
heroData() {
return {
subPageIntro: this.headerData?.fields?.heroSection?.fields.introText,
}
},
},
};
</script>