How to populate an array BEFORE mounted lifecycle hook - vue.js

I have a simple API call in getFeedingsAgain. I am calling it inside "beforeMount" yet I can see via my console.logs that my data value for "catFeedingsAgain" stays empty once "mounted" is called. I am trying to populate the catFeedingsAgain array BEFORE the component's "mounted()" lifecycle hook is called so a function I have inside the "mounted()" hook can use that array data. How can I make this work?
Thank you for your time.
UPDATED: including entire component code now.
NOTE: I am basically trying to replace the initial array within Amcharts...Line Chart #2...."dataProvider" with the resultant array from the API call in getFeedingsAgain.
```
<template>
<!-- second chart group -->
<div class="chart-block" style="padding-top:50px">
{{ message }}
<div ref="line" style="vertical-align: middle; display: inline-block; width: 50%; height: 30px;"></div>
<div ref="column" style="vertical-align: middle;display: inline-block; width: 50%; height: 30px;"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: ['message'],
name: 'app',
computed:{
},
created(){
this.getFeedingsAgain(this.message);
},
data() {
return {
chartCatID: this.message,
catFeedingsAgain: [],
catMedicationsAgain: [],
}
},
mounted () {
/**
* Line Chart #2
*/
// this.getFeedingsAgain(this.message);
console.log("mounted");
console.log(this.catFeedingsAgain);
// TODO: line = weight(waf) / day(created?)
AmCharts.makeChart( this.$refs.line, {
"type": "serial",
"dataProvider": [ {
"day": 1,
"weight_after_food": 120
}, {
"day": 2,
"weight_after_food": 54
}, {
"day": 3,
"weight_after_food": -18
}, {
"day": 4,
"weight_after_food": -12
}, {
"day": 5,
"weight_after_food": -51
}, {
"day": 6,
"weight_after_food": 12
}, {
"day": 7,
"weight_after_food": 56
}, {
"day": 8,
"weight_after_food": 113
}, {
"day": 9,
"weight_after_food": 142
}, {
"day": 10,
"weight_after_food": 125
} ],
"categoryField": "day",
"autoMargins": false,
"marginLeft": 0,
"marginRight": 5,
"marginTop": 0,
"marginBottom": 0,
"graphs": [ {
"valueField": "weight_after_food",
"showBalloon": false,
"lineColor": "#ffbf63",
"negativeLineColor": "#289eaf"
} ],
"valueAxes": [ {
"gridAlpha": 0,
"axisAlpha": 0,
"guides": [ {
"weight_after_food": 0,
"lineAlpha": 0.1
} ]
} ],
"categoryAxis": {
"gridAlpha": 0,
"axisAlpha": 0,
"startOnAxis": true
}
} );
/**
* Column Chart #2
*/
// TODO: column = dose(dosage) / day(created?)
AmCharts.makeChart( this.$refs.column, {
"type": "serial",
"dataProvider": [ {
"day": 1,
"value": -5
}, {
"day": 2,
"value": 3
}, {
"day": 3,
"value": 7
}, {
"day": 4,
"value": -3
}, {
"day": 5,
"value": 3
}, {
"day": 6,
"value": 4
}, {
"day": 7,
"value": 6
}, {
"day": 8,
"value": -3
}, {
"day": 9,
"value": -2
}, {
"day": 10,
"value": 6
} ],
"categoryField": "day",
"autoMargins": false,
"marginLeft": 0,
"marginRight": 0,
"marginTop": 0,
"marginBottom": 0,
"graphs": [ {
"valueField": "value",
"type": "column",
"fillAlphas": 1,
"showBalloon": false,
"lineColor": "#ffbf63",
"negativeFillColors": "#289eaf",
"negativeLineColor": "#289eaf"
} ],
"valueAxes": [ {
"gridAlpha": 0,
"axisAlpha": 0
} ],
"categoryAxis": {
"gridAlpha": 0,
"axisAlpha": 0
}
} );
},
methods:{
getFeedingsAgain(value) {
axios.get(`${process.env.KITTY_URL}/api/v1/feedings/?cat__slug&cat__name=${value}`)
.then(response => {
console.log("getFeedingsAgain: ");
console.log(response.data.results);
this.catFeedingsAgain = response.data.results
})
.catch(error => console.log(error));
},
getMedicationsAgain(value) {
axios.get(`${process.env.KITTY_URL}/api/v1/medications/?cat__slug=&cat__name=${value}`)
.then(response => {console.log("catMedications: ");console.log(response.data.results); this.catMedications = response.data.results})
.catch(error => console.log(error));
},
}
}
</script>
<style>
.amcharts-chart-div a{
text-indent: -9999px;
outline: none;
}
</style>
```

You could also create a watcher, to perform an action when the variable changes.
something like this
watch: {
catFeedingsAgain: function() {
AmCharts.makeChart( this.$refs.line, {
"type": "serial",
"dataProvider": this.catFeedingsAgain,
...
});
}
},
you can find the documentation here; https://v2.vuejs.org/v2/guide/computed.html#Watchers

First, you should not use beforeMount() method. Avoid it as much as you can. Instead use created() life-cycle method:
created() {
this.getFeedingsAgain(this.message);
}
Second, you want to populate an array before mounted is called. But since your data is being loaded from API asynchronously, you cannot do that. You cannot stop/pause mounted event from happening. Vue.js doesn't know when API response will be available. It could take 2 seconds, 3 seconds or simply fail.
The only option you have is to use v-if in your template. As long as your array length is zero, you can hide DOM elements or show loading progress bar.
First, you should not use beforeMount() method. Avoid it as much as you can. Instead use created() life-cycle method:
created() {
this.getFeedingsAgain(this.message);
}
Second, you want to populate an array before mounted is called. But since your data is being loaded from API asynchronously, you cannot do that. You cannot stop/pause mounted event from happening. Vue.js doesn't know when API response will be available. It could take 2 seconds, 3 seconds or simply fail.
The only option you have is to use v-if in your template. As long as your array length is zero, you can hide DOM elements or show loading progress bar.
Finally, you may want to initialize some sort of Chart, then you can use Vue.js watcher or initialize it within the then() block of the API.
EDIT
I have to do this, then I will use watcher if I wish to initialize Charting component on every change like this:
watch: {
catFeedingsAgain() {
AmCharts.makeChart(this.$refs.line, {
"type": "serial",
"dataProvider": this.catFeedingsAgain,
});
}
},
If I have to do this only once, then I would something like this:
methods: {
getFeedingsAgain(value) {
axios.get(`${process.env.KITTY_URL}/api/v1/feedings/?cat__slug&cat__name=${value}`)
.then(response => {
this.catFeedingsAgain = response.data.results;
AmCharts.makeChart(this.$refs.line, {
"type": "serial",
"dataProvider": this.catFeedingsAgain,
});
})
.catch(error => console.log(error));
},
}
Both are right approaches. It all depends upon the context.

You should fetch the data when the view is created and the data is already being observed and this is happens at created hook.
// rename beforeMount -> created
created(){
this.getFeedingsAgain(this.message);
}
From Vue created docks
At this stage, the instance has finished processing the options which
means the following have been set up: data observation, computed
properties, methods, watch/event callbacks. However, the mounting
phase has not been started
Seems like this is what you are looking for

Related

"Maximum call stack size" Error When Adding Data to Chartjs Using Props With Fetch (Vue/Chartjs)

I am trying to use a method to fetch data from a json file and add it to my chart.js chart. I keep getting a "Maximum call stack size exceeded", this is specifically caused by the this.chartData.push(el.value); line, I've tried changing naming around to no success as well as using this.$data.chartData.
I am using vue3, chart.js v3 and j-t-mcc/vue3-chartjs
here is a codesandbox.io of the code with the error.
Child (chart) component
<template>
<div class="card card-body bg-dark">
<div class="col" id="chart">
<vue3-chart-js
ref="chartRef"
:id="sampleChart.id"
:type="sampleChart.type"
:data="sampleChart.data"
:options="sampleChart.options"
></vue3-chart-js>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import Vue3ChartJs from "#j-t-mcc/vue3-chartjs"
import 'chartjs-adapter-date-fns';
var chartOptions = {
maintainAspectRatio: true,
responsive: true,
animation: {
duration: 500
},
plugins: {
legend: {
display: false,
},
tooltip: {
yAlign: "bottom",
},
},
interaction: {
mode: "index",
intersect: false,
axis: "x",
},
scales: {
x: {
type: "time",
time: {
unit: "minute"
}
},
y: {
beginAtZero: true,
},
},
elements: {
point: {
pointRadius: 5.0,
},
},
layout: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 10,
},
},
}
export default {
name: "Chart",
components: {
Vue3ChartJs,
},
props: {
chartData: Array,
chartLabels: Array
},
setup(props) {
const chartRef = ref(null)
console.log("area chart data", props.chartData)
const chartDetails = {
labels: props.chartLabels,
fill: true,
datasets: [
{
label: "",
data: props.chartData,
borderColor: "rgb(24, 144, 255)",
tension: 0.1,
fill: true,
},
],
}
const sampleChart = {
id: "line",
type: "line",
data: chartDetails,
options: chartOptions,
}
return {
sampleChart,
chartRef
}
},
watch: {
chartLabels: {
deep: true,
handler() {
this.chartRef.update(250)
}
}
},
}
</script>
<style>
#chart {
position: relative;
margin: auto;
height: 100%;
width: 100%;
}
</style>
Parent component
<template>
<div>
<div class="container-fluid">
<SampleChart :chart-data="chartData" :chart-labels="chartLabels" />
</div>
</div>
</template>
<script>
import SampleChart from "./SampleChart.vue";
export default {
components: { SampleChart },
data() {
return {
chartData: [],
chartLabels: [],
};
},
async beforeMount() {
this.getTimelineData();
},
methods: {
getTimelineData: function () {
fetch("http://localhost:8080/sample.json")
.then((res) => res.json())
.then((data) => {
data.data.forEach((el) => {
this.chartData.push(el.value);
this.chartLabels.push(el.timestamp);
});
});
},
},
};
</script>
Package.json dependencies
"dependencies": {
"#j-t-mcc/vue3-chartjs": "^1.1.2",
"bootstrap": "^5.0.2",
"chart.js": "^3.3.2",
"chartjs-adapter-date-fns": "^2.0.0",
"core-js": "^3.6.5",
"date-fns": "^2.23.0",
"leaflet": "^1.7.1",
"vue": "^3.1.5"
}
The Error Message
Uncaught (in promise) RangeError: Maximum call stack size exceeded
at Object.get (reactivity.esm-bundler.js?a1e9:231)
at toRaw (reactivity.esm-bundler.js?a1e9:743)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
Sample method without fetch that worked fine
getTestData: function () {
var labels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var values = [10, 25, 39, 55, 90, 202, 304, 202, 105, 33, 44, 95, 20, 39, 90];
labels.forEach((el) => {
this.chartLabels.push(el);
});
values.forEach((el) => {
this.chartData.push(el);
});
},
Json data sample
{
"data": [
{
"timestamp": 1627382793000,
"value": 121
},
{
"timestamp": 1627383698000,
"value": 203
},
{
"timestamp": 1627387917000,
"value": 15
}
]
}
it's work when adding a simple v-if with a ready property that we turn it true when we finish the foreach of pushing data,
the problem is with your SampleChart.vue componenent , you make chart data inside the setup , so when data changed sampleChart will not be changed in any case , it's already calculated.
you can learn more about computed, ref/reactive
While Hossem's answer will work for the first render, the chart still wont be updated when you add new data.
Oddly enough, downgrading Vue one version from 3.1.5 to 3.1.4 ended up resolving the issue.

Vue FullCalendar: Date and Event doubleclick handler

I'm using Vue FullCalendar 5.3.1. I want to add event on doubleclick on empty date cell and edit event on doubleclick on event. How can I implement this? There are 2 methods by default: dateClick() and eventClick() and it's works fine for me.
My code:
<template>
<div>
<heading class="mb-6">Scheduler</heading>
<card class="custom-card">
<FullCalendar :options="calendarOptions"/>
</card>
</div>
</template>
<script>
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import interactionPlugin from '#fullcalendar/interaction'
import resourceTimelineDay from '#fullcalendar/resource-timeline'
export default {
components: {
FullCalendar // make the <FullCalendar> tag available
},
data() {
return {
calendarOptions: {
dateClick: function(info) {
console.log(info.dateStr)
console.log(info.resource.id)
},
eventClick: function(info) {
console.log(info)
},
height: 250,
plugins: [ dayGridPlugin, interactionPlugin, resourceTimelineDay ],
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineWeek'
},
initialView: 'resourceTimelineDay',
aspectRatio: 1.5,
editable: true,
resourceAreaColumns: [
{
field: 'title',
headerContent: 'Worker'
}
],
resources: [
{
"id": "worker_a",
"title": "Worker A"
}, {
"id": "worker_b",
"title": "Worker B",
"eventColor": "green"
}, {
"id": "worker_c",
"title": "Worker C",
"eventColor": "orange"
}
],
events: [{
"resourceId": "worker_a",
"title": "Job 5",
"start": "2020-09-15T10:00:00+00:00",
"end": "2020-09-15T15:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 2",
"start": "2020-09-15T09:00:00+00:00",
"end": "2020-09-15T14:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 4",
"start": "2020-09-15T15:30:00+00:00",
"end": "2020-09-15T17:30:00+00:00"
},
]
}
}
}
}
</script>
BTW as I noticed that now all calendar settings are passed through :options = "". If you want to pass events like so <FullCalendar :events="events"/> or handle an event like <FullCalendar #dateClick="dateClick"/>, you cannot do this. Everything needs to be passed in the calendarOptions object (documentation)
The fullcalendar doesn't provide this option.
But you can attach the double click handler, when the event object is contructed by using an Event Render Hooks
In the version 5 we can use the funcion eventDidMount
data() {
return {
calendarOptions: {
...
eventDidMount: function(eventInfo){
// Not mandatory, but you can set an id to the object
eventInfo.el.id = eventInfo.event.id;
// Set the dbclick event
eventInfo.el.ondblclick = function(){
console.log(eventInfo.event)
};
}
}
}
}
Note: This works, only because this function is called only one time, in case that you are working in other version check how many times the function is called.

How to get the correct object in an nested array in Vue.js?

I use Axios to display a JSON data and I have succeeded. But I want to show an object based on date and time, it shows now all data and I need to filter it.
So I want to look at today's date and show the object based on that, so I want to show the next upcoming event. (24/05/2020)
What I currently have:
Json:
{
"doc": [
{
"data": {
"events": {
"18807612": {
"_dt": {
"_doc": "time",
"time": "18:45",
"date": "14/05/20",
"tz": "UTC",
"tzoffset": 0,
"uts": 1566067500
},
"week": 33,
"teams": {
"home": {
"name": "name 1",
"mediumname": "name1",
"uid": 3014
},
"away": {
"name": "name 2",
"mediumname": "name 2",
"uid": 3020
}
}
},
"18807618": {
"_dt": {
"_doc": "time",
"time": "18:45",
"date": "24/05/20",
"tz": "UTC",
"tzoffset": 0,
"uts": 1566067500
},
"week": 33,
"teams": {
"home": {
"name": "name 1",
"mediumname": "name1",
"uid": 3014
},
"away": {
"name": "name 2",
"mediumname": "name2",
"uid": 3020
}
}
}
}
}
}
]
}
Store:
async loadPosts({ commit }) {
// Define urls pages
const urlEvents = 'http://api.example.com/302020';
// Set pages
const [
responseEvents
] = await Promise.all([
// Responses pages
this.$axios.get(urlEvents)
]);
// variables pages
this.events = responseEvents.data.doc[0].data.events
// mutations pages
commit('SET_EVENTS', this.events)
}
},
mutations: {
SET_EVENTS (state, events) {
state.events = events;
}
}
And to show the data I use this:
import {mapState} from 'vuex';
export default {
name: 'NextMatch',
mounted() {
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'events'
])
}
}
<h1>{{events}}</h1>
But this shows all data, and what I try to get is the first upcoming event for the object with the "uid": 3014.
So I want to show the date, time and names of the home and away team.
How can I get the correct data by filtering the data?
Something like this or similar to this should work:
In your Vue component's <template>:
`<h1>{{selectedEvent._dt.date}}</h1>`
In your Vue component's <script>:
props: {
eventID: {
type: Number,
default: 3014
},
},
computed: {
...mapState([
'events'
]),
eventsArr(){
if (!this.events) return {} //make sure Object.entries gets object.
return Object.entries(this.events)
},
selectedEvent(){
const selectedArr = this.eventsArr.find(([eID, e]) => e.teams.home.uid === this.eventID)
return selectedArr[1]
}
}

Axios get response is not carried over into state [Native React]

first question here.
when i try to run this, it does fetch data in the response.data, but that data is not set into state to be passed through as prop to another page and it always stays [Object object] or [undefined] & i have no clue what's going wrong
state = {
APiData: {},
userInput: "cheese",
onCall: false
}
findFood = () => {
let self = this;
let userInput = this.state.userInput.toLowerCase();
let url = "https://api.spoonacular.com/recipes/search?query=" + userInput + "&number=1&apiKey="+apiKey;
axios.get(url)
.then(function (response) {
console.log(response.data); //get's data
self.setState({APIdata: response.data});
})
.catch(function (error) {
console.log(error)
});
}
renderbody = () => {
console.log(this.state.APIdata) //this thing, is undefined
// return (<SearchBody data={this.state.APIdata} key={apiKey}/>)
}
this is the data in response.data
"baseUri": "https://spoonacular.com/recipeImages/",
"expires": 1585843318293,
"isStale": false,
"number": 1,
"offset": 0,
"processingTimeMs": 437,
"results": Array [
Object {
"id": 215435,
"image": "three-cheese-pizza-for-cheese-lovers-215435.jpg",
"imageUrls": Array [
"three-cheese-pizza-for-cheese-lovers-215435.jpg",
],
"readyInMinutes": 45,
"servings": 8,
"title": "Three-Cheese Pizza (For Cheese Lovers)",
},
],
"totalResults": 855,
}
Object {
"baseUri": "https://spoonacular.com/recipeImages/",
"expires": 1585843318293,
"isStale": false,
"number": 1,
"offset": 0,
"processingTimeMs": 437,
"results": Array [
Object {
"id": 215435,
"image": "three-cheese-pizza-for-cheese-lovers-215435.jpg",
"imageUrls": Array [
"three-cheese-pizza-for-cheese-lovers-215435.jpg",
],
"readyInMinutes": 45,
"servings": 8,
"title": "Three-Cheese Pizza (For Cheese Lovers)",
},
],
"totalResults": 855,
}
You are not using state. Your state needs to be inside your constructor
constructor(props) {
super(props);
this.state = {
APiData: {},
userInput: "cheese",
onCall: false
}
}
Then, you just need to call this.setState({ APIdata: response.data });
I would recommend you to use react-redux to get data using this.props.

Get Latitude and Longitude from Google Places Autocomplete in React Native

I've implemented autocomplete for my address field, but the json returned from the Google Maps Places Autocomplete doesn't include the geocoded coords for the places.
There are some answers out there that don't seem to fit. For instance, this one refers to things like google.maps.places.Autocomplete(input, options); which I don't think is a thing in React Native.
Other answers appear to be based on react-native-google-places-autocomplete, but I've implemented this myself and I'd love to not do it again using that module.
Here's my method where I call the API.
async handleAddressChange() {
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleAPIKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
this.setState({ addressPredictions: json.predictions });
} catch (err) {
console.error(err);
}
}
setAddress(prediction) {
this.setState({ address: prediction.description, showPredictions: false });
}
The API response doesn't have any place or geometry property on it:
Object {
"description": "1234 Some Avenue Northeast, Washington, DC, USA",
"id": "4c79fba1b3a5ad33478b79b54896a75a4d56ca53",
"matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"place_id": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"reference": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"structured_formatting": Object {
"main_text": "1234 Some Avenue Northeast",
"main_text_matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"secondary_text": "Washington, DC, USA",
},
"terms": Array [
Object {
"offset": 0,
"value": "1600",
},
Object {
"offset": 5,
"value": "Maryland Avenue Northeast",
},
Object {
"offset": 32,
"value": "Washington",
},
Object {
"offset": 44,
"value": "DC",
},
Object {
"offset": 48,
"value": "USA",
},
],
"types": Array [
"street_address",
"geocode",
],
}
I have found a solution for the same-
Just use like this-
<GooglePlacesAutocomplete
GooglePlacesDetailsQuery={{ fields: "geometry" }}
fetchDetails={true} // you need this to fetch the details object onPress
placeholder="Search"
query={{
key: "API_KEY_GOES_HERE",
language: "en", // language of the results
}}
onPress={(data: any, details: any = null) => {
console.log("data", data);
console.log("details", details);
console.log(JSON.stringify(details?.geometry?.location));
}}
onFail={(error) => console.error(error)} />
Once you have the place id (ChIJneQ1fBO5t4kRf8mTw4ieb4Q for the example in your question), you can do a place details request.
Make sure you include the Places library in your API call: https://maps.googleapis.com/maps/api/js?libraries=places and a valid API key.
function initialize() {
var map = new google.maps.Map(document.getElementById('map-canvas'), {
center: new google.maps.LatLng(0, 0),
zoom: 15
});
var service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJneQ1fBO5t4kRf8mTw4ieb4Q'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
// Create marker
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location
});
// Center map on place location
map.setCenter(place.geometry.location);
}
});
}
initialize();
#map-canvas {
height: 160px;
}
<div id="map-canvas"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=places&callback=initialize">
</script>