Here's the problem I'm having. I have a Leads page, that is my Leads.vue
template. It loads my leads and then passes the leads data to other components via props.
The LeadSources
component receives a computed method as it's property.
You can see that on the Leads.vue
page, the LeadSources
component calls the getSourceData()
method for it's property data.
When I check the value of the props for LeadSources.vue
in the setup()
the value for chartData
initially is an empty array. If the page hot-reloads then the LeadSources
apexchart will populate with the :series
data but otherwise I cannot get it to work.
Essentially it works like this.
Leads.vue
passes getSourceData()
to the LeadsSources.vue
component which on the setup()
sets it as the variable series
and tries to load the apexchart
with it.
It will not work if I refresh my page but if I save something in my IDE, the hot-reload will load the updated apexchart and the data appears. It seems like the prop values don't get set in the setup()
function the first time around. How do I architecturally get around this? What's the proper way to set this up? I can't tell if the issue is on the leads.vue
side of things or with the way that the LeadSources.vue
component is being put together.
Any help would be appreciated, I've spent way too long trying to get this to work properly.
Leads.vue
<template>
<!--begin::Leads-->
<div >
<div >
<LeadTracker
:lead-data="leadData"
:key="componentKey"
/>
</div>
</div>
<div >
<div >
<LeadSources
chart-color="primary"
chart-height="500"
:chart-data="getSourceData"
:chart-threshold="sourceThreshold"
widget-classes="lead-sources"
></LeadSources>
</div>
</div>
<!--end::Leads-->
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, onMounted } from "vue";
import { setCurrentPageTitle } from "@/core/helpers/breadcrumb";
import LeadSources from "@/components/leads/sources/LeadSources.vue";
import LeadTracker from "@/components/leads/tracker/LeadTracker.vue";
import LeadService from "@/core/services/LeadService";
import ApiService from "@/core/services/ApiService";
import {Lead} from "@/core/interfaces/lead";
export default defineComponent({
name: "leads",
components: {
LeadTracker,
LeadSources
},
data() {
return {
leadData: [] as Lead[],
}
},
beforeCreate: async function() {
this.leadData = await new LeadService().getLeads()
},
setup() {
onMounted(() => {
setCurrentPageTitle("Lead Activity");
});
const sourceThreshold = 5;
return {
sourceThreshold,
componentKey: 0
};
},
computed: {
getSourceData() {
interface SingleSource {
source: string;
value: number;
}
const sourceData: Array<SingleSource> = [];
// Make array for source names
const sourceTypes = [];
this.leadData.filter(lead => {
if (!sourceTypes.includes(lead.source)) sourceTypes.push(lead.source);
});
// Create objects for each form by name, push to leadSourceData
sourceTypes.filter(type => {
let totalSourceLeads = 1;
this.leadData.filter(form => {
if (form.source == type) totalSourceLeads ;
});
const leadSourceData = {
source: type,
value: totalSourceLeads
};
sourceData.push(leadSourceData);
});
// Sort by source popularity
sourceData.sort(function(a, b) {
return a.value - b.value;
});
return sourceData;
}
}
});
</script>
LeadSources.vue
<template>
<!--begin::Lead Sources Widget-->
<div : >
<!--begin::Body-->
<div
>
<div >
<!--begin::Text-->
<div >
<span >Lead Sources</span>
<span >Where your leads are coming from.</span>
<!--begin::Chart-->
<div >
<apexchart
:options="chartOptions"
:series="series"
type="donut"
:height="chartHeight"
:threshold="chartThreshold"
></apexchart>
</div>
<!--end::Chart-->
</div>
<!--begin::Unused Data-->
<div >
<div >
<div><span >Other Sources:</span></div>
<div v-for="item in otherSources" :key="item.source">
<span>{{ item.source }}</span>
<span>{{ item.value }}%</span>
</div>
</div>
<div >
<div><span >Sources Not Utilized:</span></div>
<div v-for="item in unusedSources" :key="item.source">
<span>{{ item.source }}</span>
</div>
</div>
</div>
<!--end::Unused Data-->
</div>
</div>
</div>
<!--end::Lead Sources Widget-->
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "LeadSource",
props: {
widgetClasses: String,
chartColor: String,
chartHeight: String,
chartLabels: Array,
chartData: Array,
chartThreshold: Number
},
setup(props) {
const sum = (data) => {
let total = 0;
data?.map(function(v) {
total = v;
});
return total;
}
const chartData = ref(props.chartData).value;
const threshold = ref(props.chartThreshold).value;
const usedSourcesLabel: string[] = [];
const usedSourcesData: number[] = [];
const otherSources: any = [];
const unusedSources: any = [];
const splitData = (data, max) => {
// set used, other sources < 5%, unused sources
data.filter((item) => {
if (item.value > max) {
usedSourcesLabel.push(item.source);
usedSourcesData.push(item.value);
} else if (item.value < max && item.value != 0 && item.value !== null) {
otherSources.push(item);
} else if (item.value == 0 || item.value === null) {
unusedSources.push(item);
}
});
};
splitData(chartData, threshold);
const chartOptions = {
chart: {
width: 380,
type: "donut"
},
colors: [
"#1C6767",
"#CD2E3B",
"#154D5D",
"#F1D67E",
"#4F9E82",
"#EF8669",
"#393939",
"#30AEB4"
],
plotOptions: {
pie: {
startAngle: -90,
endAngle: 270
}
},
dataLabels: {
enabled: false
},
fill: {
type: "gradient",
gradient: {
type: "horizontal",
shadeIntensity: 0.5,
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100],
}
},
legend: {
show: true,
position: "left",
fontSize: "16px",
height: 220,
onItemClick: {
toggleDataSeries: false
},
onItemHover: {
highlightDataSeries: false
},
formatter: function (val, opts) {
return val " - " opts.w.globals.series[opts.seriesIndex];
}
},
title: {
text: undefined
},
tooltip: {
style: {
fontSize: "14px"
},
marker: {
show: false
},
y: {
formatter: function(val) {
return val "%";
},
title: {
formatter: (seriesName) => seriesName,
},
}
},
labels: usedSourcesLabel,
annotations: {
position: "front",
yaxis: [{
label: {
text: "text annotation"
}
}],
xaxis: [{
label: {
text: "text xaxis annotation"
}
}],
},
responsive: [{
breakpoint: 480,
options: {
legend: {
position: "bottom",
horizontalAlign: "left"
}
}
}]
};
const series = usedSourcesData;
return {
chartOptions,
series,
otherSources,
unusedSources
};
}
});
</script>
Edited
I will attach the LeadService.ts
class as well as the ApiService.ts
class so you can see where the data is coming from
LeadService.ts
import ApiService from "@/core/services/ApiService";
import {Lead} from "@/core/interfaces/lead";
export default class LeadService {
getLeads() {
const accountInfo = JSON.parse(localStorage.getItem('accountInfo') || '{}');
ApiService.setHeader();
return ApiService.query("/leads", {params: {client_id : accountInfo.client_id}})
.then(({ data }) => {
let leadData: Lead[] = data['Items'];
return leadData;
})
.catch(({ response }) => {
return response;
});
}
}
ApiService.ts
import { App } from "vue";
import axios from "axios";
import VueAxios from "vue-axios";
import JwtService from "@/core/services/JwtService";
import { AxiosResponse, AxiosRequestConfig } from "axios";
import auth from "@/core/helpers/auth";
/**
* @description service to call HTTP request via Axios
*/
class ApiService {
/**
* @description property to share vue instance
*/
public static vueInstance: App;
/**
* @description initialize vue axios
*/
public static init(app: App<Element>) {
ApiService.vueInstance = app;
ApiService.vueInstance.use(VueAxios, axios);
ApiService.vueInstance.axios.defaults.baseURL = "https://api.domain.com/";
}
/**
* @description set the default HTTP request headers
*/
public static setHeader(): void {
ApiService.vueInstance.axios.defaults.headers.common[
"Authorization"
] = `Bearer ${auth.getSignInUserSession().getIdToken().jwtToken}`;
ApiService.vueInstance.axios.defaults.headers.common[
"Content-Type"
] = "application/json application/vnd.api json";
}
/**
* @description send the GET HTTP request
* @param resource: string
* @param params: AxiosRequestConfig
* @returns Promise<AxiosResponse>
*/
public static query(
resource: string,
params: AxiosRequestConfig
): Promise<AxiosResponse> {
return ApiService.vueInstance.axios.get(resource, params).catch(error => {
// @TODO log out and send home if response is 401 bad auth
throw new Error(`[KT] ApiService ${error}`);
});
}
}
export default ApiService;
CodePudding user response:
I think the issue is caused when you're calling the data from the api. This code:
beforeCreate: async function() {
this.leadData = await new LeadService().getLeads()
},
I'd refactor to
async created () {
const service = new LeadService()
const value = await service.getLeads()
}
Also it would be nice to be able to see how you're fetching your data.
Sometimes this code: const value = await axios.get('/api/getStuff').data
can be problematic because of paratheses issues. Which causes the same issue you described of, hot reloading working, but fresh not. I suspect the same sort of issue relies of the code executing like => (await new LeadService()).getLeads()
Where you're probably awaiting the class, rather than the actual async code.