This is my script of a VueJS component which should show data inside a dashboard.
<script>
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import exportingInit from "highcharts/modules/exporting";
//import stockInit from "highcharts/modules/stock";
//stockInit(Highcharts);
exportingInit(Highcharts);
export default {
props: {
partsdata: {
type: Array
}
},
methods: {
fetchData() {
this.$http.get('http://127.0.0.1:5000/data/')
.then(response => {
return response.json();
}).then(data => {
var result = JSON.parse(data) // eslint-disable-line no-console
console.log(result.Fare) // eslint-disable-line no-console
})
}
},
components: {
highcharts: Chart
},
data() {
return {
chartOptions: {
series: [{
data: [] // sample data
}]
}
}
}
};
</script>
Receiving the data from my backend works fine, but I am unable to get it into the chart. I still struggle how to work with async tasks, so I tried this and failed
this.data.push(result.Fare)
Header.vue?5e07:33 Uncaught (in promise) TypeError: Cannot read
property 'push' of undefined
at VueComponent.eval (Header.vue?5e07:34)
Can anyone tell me how to do this?
Update: This is my template where I trigger the Method:
<template>
<div>
<button #click="fetchData" class="btn-primary">Get Data</button>
<highcharts
:options="chartOptions"
></highcharts>
</div>
</template>
Looking at your code if you're trying this.data.push its understandable that its telling you its undefined.
To get to your data prop in your fetchData method youd have to use
this.chartOptions.series[0].data.push
I'm not sure where you're firing fetchData either in that code. Try move it to your created/mounted methods in the vue lifecycle, you can then change your chartOptions to a computed or watcher, setting data in the data() method and calling it in your chartOptions with this.data.
UPDATE:
On click is fine for your get request if it's firing keep that the same, its where you're then setting it thats the issue.
I refactored your request, if its successful when you test it response.data should give you the results you need, not sure why you need to parse it unless its returning a json blob as a string?
I also added a v-if to your chart renderer, if this.data is empty then don't render. See how you get on with that.
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import exportingInit from "highcharts/modules/exporting";
//import stockInit from "highcharts/modules/stock";
//stockInit(Highcharts);
exportingInit(Highcharts);
export default {
props: {
partsdata: {
type: Array
}
},
methods: {
fetchData() {
this.$http.get('http://127.0.0.1:5000/data/')
.then(response => {
this.data.push(JSON.parse(response.data))
})
}
},
watch: {
data: function() {
this.chartOptions.series[0].data.push(this.data)
}
},
components: {
highcharts: Chart
},
data() {
return {
data: [],
chartOptions: {
series: [{
data: [] // sample data
}]
}
}
}
};
</script>
<template>
<div>
<button #click="fetchData" class="btn-primary">Get Data</button>
<highcharts if="data.length > 0" :options="chartOptions" ></highcharts>
</div>
</template>
You need to correctly get data and change chartOptions:
<template>
<div>
<highcharts class="hc" :options="chartOptions" ref="chart"></highcharts>
<button #click="fetchData" class="btn-primary">Get Data</button>
</div>
</template>
<script>
export default {
methods: {
fetchData() {
fetch('https://api.myjson.com/bins/ztyb0')
.then(response => {
return response.json()
})
.then(data => {
this.chartOptions.series[0].data = data;
})
}
},
data() {
return {
chartOptions: {
series: [{
data: []
}]
}
};
}
};
</script>
Live demo: https://codesandbox.io/s/highcharts-vue-demo-05mpo
Docs: https://www.npmjs.com/package/highcharts-vue
Related
datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props
I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}
Hello I have a component Carousel, and I have method:
carousel () {
return this.$refs.carousel
}
When I try pass to prop of other component, I get undefined in prop data, why?
<Dots :carousel="carousel()" />
In child component I have:
export default {
name: 'Dots',
props: ['carousel']
}
In mounted when I try call console.log(this.carousel), I get undifined. But I need get a component data of Carousel. How I can do it?
Sandbox: https://codesandbox.io/s/flamboyant-monad-5bhjz?file=/src/components/Test.vue
You can set a function in carousel to return the data you want to send to dots. Then, in the parent component, set its return value to an attribute in data which will be passed as a prop:
const dotsComponent = Vue.component('dotsComponent', {
template: '#dotsComponent',
props: ['carousel']
});
const carouselComponent = Vue.component('carouselComponent', {
template: '#carouselComponent',
data() { return { id:1 } },
methods: {
getData() { return { id: this.id } }
}
});
new Vue({
el: "#app",
components: { dotsComponent, carouselComponent },
data() { return { carousel:null } },
mounted() { this.carousel = this.$refs.carousel.getData(); }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div><carousel-component ref="carousel"/></div>
<div><dots-component :carousel="carousel"/></div>
</div>
<template id="dotsComponent"><p>Dots: {{carousel}}</p></template>
<template id="carouselComponent"><p>Carousel</p></template>
You could pass the ref directly without using a method any other property :
<Dots :carousel="$refs.carousel" />
Background: I've built a standard single file component that takes a name prop and looks in different places my app's directory structure and provides the first matched component with that name. It was created to allow for "child theming" in my Vue.js CMS, called Resto. It's a similar principle to how WordPress looks for template files, first by checking the Child theme location, then reverting to the parent them if not found, etc.
Usage : The component can be used like this:
<!-- Find the PageHeader component
in the current child theme, parent theme,
or base components folder --->
<theme-component name="PageHeader">
<h1>Maybe I'm a slot for the page title!</h1>
</theme-component>
My goal : I want to convert to a functional component so it doesn't affect my app's render performance or show up in the Vue devtools. It looks like this:
<template>
<component
:is="dynamicComponent"
v-if="dynamicComponent"
v-bind="{ ...$attrs, ...$props }"
v-on="$listeners"
#hook:mounted="$emit('mounted')"
>
<slot />
</component>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ThemeComponent',
props: {
name: {
type: String,
required: true,
default: '',
},
},
data() {
return {
dynamicComponent: null,
resolvedPath: '',
}
},
computed: {
...mapGetters('site', ['getThemeName']),
customThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying custom theme component for ${this.customThemePath}`)
return () => import(`#themes/${this.customThemePath}`)
},
defaultThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying default component for ${this.name}`)
return () => import(`#restoBaseTheme/${this.componentPath}`)
},
baseComponentLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying base component for ${this.name}`)
return () => import(`#components/Base/${this.name}`)
},
componentPath() {
return `components/${this.name}`
}, // componentPath
customThemePath() {
return `${this.getThemeName}/${this.componentPath}`
}, // customThemePath()
},
mounted() {
this.customThemeLoader()
.then(() => {
// If found in the current custom Theme dir, load from there
this.dynamicComponent = () => this.customThemeLoader()
this.resolvedPath = `#themes/${this.customThemePath}`
})
.catch(() => {
this.defaultThemeLoader()
.then(() => {
// If found in the default Theme dir, load from there
this.dynamicComponent = () => this.defaultThemeLoader()
this.resolvedPath = `#restoBaseTheme/${this.defaultThemePath}`
})
.catch(() => {
this.baseComponentLoader()
.then(() => {
// Finally, if it can't be found, try the Base folder
this.dynamicComponent = () => this.baseComponentLoader()
this.resolvedPath = `#components/Base/${this.name}`
})
.catch(() => {
// If found in the /components dir, load from there
this.dynamicComponent = () => import(`#components/${this.name}`)
this.resolvedPath = `#components/${this.name}`
})
})
})
},
}
</script>
I've tried SO many different approaches but I'm fairly new to functional components and render functions (never got into React).
The roadblock : I can't seem to figure out how to run the chained functions that I call in my original mounted() function. I've tried running it from inside the render function with no success.
Big Question
How can I find and dynamically import the component I'm targeting before I pass that component to the createElement function (or within my single file <template functional><template/>)?
Thanks all you Vue-heads! ✌️
Update: I stumbled across this solution for using the h() render function and randomly loading a component, but I'm not sure how to make it work to accept the name prop...
Late to the party, but I was in a similar situation, where I had a component in charge of conditionally render one of 11 different child components:
<template>
<v-row>
<v-col>
<custom-title v-if="type === 'title'" :data="data" />
<custom-paragraph v-else-if="type === 'paragraph'" :data="data" />
<custom-text v-else-if="type === 'text'" :data="data" />
... 8 more times
</v-col>
</v-row>
</template>
<script>
export default {
name: 'ProjectDynamicFormFieldDetail',
components: {
CustomTitle: () => import('#/modules/path/to/CustomTitle'),
CustomParagraph: () => import('#/modules/path/to/CustomParagraph'),
CustomText: () => import('#/modules/path/to/CustomText'),
... 8 more times
},
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
default: null,
}
},
}
</script>
which of course is not ideal and pretty ugly.
The functional equivalent I came up with is the following
import Vue from 'vue'
export default {
functional: true,
props: { type: { type: String, required: true }, data: { type: Object, default: null } },
render(createElement, { props: { type, data } } ) {
// prop 'type' === ['Title', 'Paragraph', 'Text', etc]
const element = `Custom${type}`
// register the custom component globally
Vue.component(element, require(`#/modules/path/to/${element}`).default)
return createElement(element, { props: { data } })
}
}
Couple of things:
lazy imports don't seem to work inside Vue.component, hence require().default is the way to go
in this case the prop 'type' needs to be formatted, either in the parent component or right here
I am trying to use a computed property in order to filter the results that are being displayed when using a v-for loop. However, I still see all the bars that are decleared in my bars.js on my application. I was expecting to only see one as it is set to 'true'. Therefore, where am I goinf wrong? Any help welcome.
I am following https://medium.com/devmarketer/how-to-add-conditional-statements-to-v-for-loops-in-vue-js-c0b4d17e7dfd
<li v-for="bar in bars" :key="bar.id">
<h2>{{ bar.barName }}</h2>
</li>
<script>
import bars from "./../data/bars";
export default {
data() {
return {
bars: bars
};
},
computed: {
bars: function() {
return this.default.filter(function(u) {
return u.newListing
})
}
}
};
</script>
In an additional file called bars.js I have the following;
export default [
{
barName: "Mikes",
newListing: true,
},
{
barName: "Cheers",
newListing: false,
},
{
barName: "Biker Bar",
newListing: false,
}
];
You have two bars in your code, so it's not clear which the template should use. It looks like it's opting for the bars in data. You should rename the computed property to something unique and then us that in the template. For example:
export default {
data() {
return {
bars: [/* your bar data goes here */]
};
},
computed: {
bars_filtered: function() {
return this.bars.filter(u => u.newListing)
}
}
};
Then in your template you would loop over bars_filtered:
v-for="bar in bars_filtered"