Background:
I have a child component that receives an array called expenseButton as props. Within the array are objects with values which I am trying to get the sum of using array.reduce()
Problem
When I use methods to get the sum of the values it works perfectly fine but when I try to make it a computed property I get an error that states:
("test" is the name of the computed property)
Property or method "test" is not defined on the instance but referenced during render
<script>
export default {
props: {
expenseButton: Array,
},
data() {
return {
chosenExpenseId: null
};
},
computed: {
test() {
return this.expenseButton.reduce((acc, curr) => {
acc += curr.expensesValue;
return acc;
}, 0);
}
}
}
};
</script>
<template>
<div>
<div class="yourBalance">
Your monthly balance
<br />
<span>${{ test }}</span>
</div>
</div>
<template>
UPDATE
The "expenseValue" property within the "expenseButton" array is coming from a database on the backend using axios
Parent component
<template>
<div>
<expense-button :myExpense="myExpense" :expenseButton="expenseButton"></expense-button>
</div>
</template>
<script>
import Expenses from "../components/expenses.vue";
import axios from "axios";
export default {
components: {
"expense-button": Expenses
},
data() {
return {
budgetOwner: "",
myExpense: [],
expenseButton: [],
component: "",
errored: false
};
},
beforeRouteEnter(to, from, next) {
axios
.get("/api/budget", {
headers: { "Content-Type": "application/json" },
withCredentials: true
})
.then(res => {
next(vm => {
if (res.data.budget.length > 0) {
vm.myExpense = res.data.budget;
vm.expenseButton = res.data.budget[0].expenses;
}
});
})
.catch(err => {
next(vm => {
console.log(err.response);
vm.errored = true;
});
});
}
}
</script>
Data from database
"budget":[{"expenses":[
{"expensesKey":"a","expensesValue":1,"subExpenses":"","newValue":""},
{"expensesKey":"b","expensesValue":2,"subExpenses":"","newValue":""},
{"expensesKey":"c","expensesValue":3,"subExpenses":"","newValue":""}
]
Try this
test() {
if(this.expenseButton){
return this.expenseButton.reduce((acc, curr) => {
acc += curr.expensesValue;
return acc;
}, 0);
}
else{
return ''
}
}
Try to help you. The problem will be in curr.expensesValue. What is expensesValue? And one more question. Are you mount right your app? Are you have the same id in the root like a el:'#app' and div#id in my example?
new Vue({
el: "#app",
data:{
expenseButton:[1,2,3,4,5],
chosenExpenseId: null
},
computed: {
test() {
return this.expenseButton.reduce((acc, curr) => acc + curr)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="yourBalance">
Your monthly balance
<br />
<span>{{ test }}</span>
</div>
</div>
Related
Iam starting with this hobby in VUE3 :) I try summary item books.price from object Object is in parent component and I want make this in child components. I have object in App.vue:
<script>
import BooksList from './components/BooksList.vue'
import BooksLengthMsg from './components/BooksLengthMsg.vue'
import booksSummary from './components/BookSummary.vue'
// import { computed } from 'vue'
export default {
components: { BooksList, BooksLengthMsg, booksSummary },
name: 'App',
data: () => ({
books: [
{
title: 'Windows Powershell w miesiąc',
price: 20
},
{
title: 'Alicja w krainie czarów',
price: 18
}
],
form: {
title: '',
price: null
}
}),
methods: {
removeBook (index) {
this.books.splice(index, 1)
console.log('delete', index)
},
handleSubmit () {
const newBook = { ...this.form }
this.books.push(newBook)
this.resetForm()
},
resetForm () {
this.form.price = null
this.form.title = ''
}
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div id="app">
<header>
<h1>Books<span>.app</span></h1>
</header>
<books-list #remove="removeBook" :books="books" />
<books-length-msg :books="books"/>
<form #submit.prevent="handleSubmit">
<label>
Title:
<input v-model="form.title" type="text" name="title">
</label>
<label>
Price:
<input v-model="form.price" type="number" name="price">
</label>
<button type="submit">Add book</button>
</form>
</div>
<books-summary :books="books" />
</template>
and I maked new component BookSummary and I run Books Amount but I cant run function for total price, I cant save in const priceSummary.
<script>
import { ref } from 'vue'
export default {
name: 'BooksSummary',
setup () {
const priceSummary = ref(0)
return { priceSummary }
},
props: {
books: {
type: Array,
required: true
}
},
computed: {
bookAmount () {
return this.books.length
},
totalPrice () {
// const totalPr = this.priceSummary
// return this.books.forEach((book) => book.price)
return this.books.forEach((book) => { console.log(book.price) })
}
}
}
</script>
<template>
<div>Books Amount : {{ bookAmount }}</div>
<div>Total price : const priceSummary:{{ priceSummary}} function {{ totalPrice }}</div>
</template>
<style>
div {
padding: 5px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
It was stupid facil error I change { this.priceSummary += book.price }), but now I must make it with reduce (thx for answer) and make parseInt.
<script>
import { ref } from 'vue'
export default {
name: 'BooksSummary',
setup () {
const priceSummary = ref(0)
return { priceSummary }
},
props: {
books: {
type: Array,
required: true
}
},
computed: {
bookAmount () {
return this.books.length
},
totalPrice () {
return this.books.forEach((book) => { this.priceSummary += book.price })
}
}
}
</script>
<template>
<div>Books Amount : {{ bookAmount }}</div>
<div>Total price : {{ priceSummary}} {{ totalPrice }}</div>
</template>
<style>
div {
padding: 5px;
}
</style>
You need to change your totalPrice computed property to:
totalPrice () {
this.priceSummary = this.books.reduce((sum, book) => sum + book.price, 0)
return this.priceSummary;
}
Using .reduce is fairly standard and performant way to get a total from array
Here is reference for the reduce function.
I'm trying to fetch data from graphQL, and I know that by putting function into the react UseEffect(), I would be able to call the function once the data is updated and constructed.
However, I'm working on a chatroom, and the data does not appear on the screen:
import {
CREATE_MESSAGE_MUTATION,
MESSAGES_QUERY,
MESSAGE_SUBSCRIPTION,
} from "../graphql";
import { useQuery, useSubscription } from "#apollo/client";
import React, { useEffect } from "react";
import { Tag } from "antd";
const ChatBox = ({ me, friend, ...props }) => {
//me friend are strings
const chatBoxName = [me, friend].sort().join("_");
const { loading, error, data, subscribeToMore } = useQuery(MESSAGES_QUERY, {
variables: { name: chatBoxName },
});
useEffect(() => {
try {
subscribeToMore({
document: MESSAGE_SUBSCRIPTION,
variables: { name: chatBoxName },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newMessage = subscriptionData.data;
console.log("Subscribing more data: ", newMessage);
},
});
} catch (e) {
console.log("Error in subscription:", e);
}
}, [subscribeToMore]);
if (loading) return <p>loading ...</p>;
if (error) return <p>Error in frontend chatbox: {error}</p>;
return (
<div className="App-messages">
{console.log(data.chatboxs[0].messages)}
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => {
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
})}
</div>
);
};
export default ChatBox;
After a small delay of loading ..., it turns to the <div className="App-messages"> with no messages inside. However, on the console I can clearly see the messages that I want to print.
What is the problem of the function in UseEffect()? I would be so appreciated if anyone can help .
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => { // <- this part
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
})}
As a callback, you declared a function that does not return JSX elements.
Replace with this
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => (
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
))}
I need to render a map using Mapbox only when data is ready.
I have the following code in my Vuex store:
/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import _ from "lodash";
import { backendCaller } from "src/core/speakers/backend";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// Activity
activity: [],
geoIps: [],
},
mutations: {
// Activity
setActivity: (state, value) => {
state.activity = value;
},
setGeoIp: (state, value) => {
state.geoIps.push(value);
},
},
actions: {
// Activity
async FETCH_ACTIVITY({ commit, state }, force = false) {
if (!state.activity.length || force) {
await backendCaller.get("activity").then((response) => {
commit("setActivity", response.data.data);
});
}
},
async FETCH_GEO_IPS({ commit, getters }) {
const geoIpsPromises = getters.activityIps.map(async (activityIp) => {
return await Vue.prototype.$axios
.get(
`http://api.ipstack.com/${activityIp}?access_key=${process.env.IP_STACK_API_KEY}`
)
.then((response) => {
return response.data;
});
});
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
});
});
},
},
getters: {
activityIps: (state) => {
return _.uniq(state.activity.map((activityRow) => activityRow.ip));
},
},
strict: process.env.DEV,
});
In my App.vue I fetch all APIs requests using an async created method.
App.vue:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: "App",
async created() {
await this.$store.dispatch("FETCH_ACTIVITY");
await this.$store.dispatch("FETCH_GEO_IPS");
},
};
</script>
In my Dashboard component I have a conditional rendering to draw the maps component only when geoIps.length > 0
Dashboard.vue:
<template>
<div v-if="geoIps.length > 0">
<maps-geo-ips-card />
</div>
</template>
<script>
import mapsGeoIpsCard from "components/cards/mapsGeoIpsCard";
export default {
name: "dashboard",
components: {
mapsGeoIpsCard,
},
computed: {
activity() {
return this.$store.state.activity;
},
activityIps() {
return this.$store.getters.activityIps;
},
geoIps() {
return this.$store.state.geoIps;
},
};
</script>
Then I load the Maps component.
<template>
<q-card class="bg-primary APP__card APP__card-highlight">
<q-card-section class="no-padding no-margin">
<div id="map"></div>
</q-card-section>
</q-card>
</template>
<script>
import "mapbox-gl/dist/mapbox-gl.css";
import mapboxgl from "mapbox-gl/dist/mapbox-gl";
export default {
name: "maps-geo-ips-card",
computed: {
geoIps() {
return this.$store.state.geoIps;
},
},
created() {
mapboxgl.accessToken = process.env.MAPBOX_API_KEY;
},
mounted() {
const mapbox = new mapboxgl.Map({
container: "map",
center: [0, 15],
zoom: 1,
});
this.geoIps.map((geoIp) =>
new mapboxgl.Marker()
.setLngLat([geoIp.longitude, geoIp.latitude])
.addTo(mapbox)
);
},
};
</script>
<style>
#map {
height: 500px;
width: 100%;
border-radius: 25px;
overflow: hidden;
}
</style>
The problem is that when the function resolves the first IP address, the map is drawn showing only one address and not all the others like this:
What is the best way to only draw the map when my FETCH_GEO_IPS function has finished?
Thanks in advance
I think the answer lies in this bit of code:
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
});
});
Your map function loops through every element of the array and commits each IP one by one. So when the first one is committed, your v-if="geoIps.length > 0" is true.
A workaround would be to set a flag only when the IPs are set.
This is a proposed solution:
import Vue from "vue";
import Vuex from "vuex";
import _ from "lodash";
import { backendCaller } from "src/core/speakers/backend";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// Activity
activity: [],
geoIps: [],
isReady: false
},
mutations: {
// Activity
setActivity: (state, value) => {
state.activity = value;
},
setGeoIp: (state, value) => {
state.geoIps.push(value);
},
setIsReady: (state, value) => {
state.isReady = value;
}
},
actions: {
// Activity
async FETCH_ACTIVITY({ commit, state }, force = false) {
if (!state.activity.length || force) {
await backendCaller.get("activity").then((response) => {
commit("setActivity", response.data.data);
});
}
},
async FETCH_GEO_IPS({ commit, getters }) {
let tofetch = getters.activityIps.length; // get the number of fetch to do
const geoIpsPromises = getters.activityIps.map(async (activityIp) => {
return await Vue.prototype.$axios
.get(
`http://api.ipstack.com/${activityIp}?access_key=${process.env.IP_STACK_API_KEY}`
)
.then((response) => {
return response.data;
});
});
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
toFetch -= 1; // decrement after each commit
if (toFetch === 0) {
commit("setIsReady", true); // all commits are done
}
});
});
},
},
getters: {
activityIps: (state) => {
return _.uniq(state.activity.map((activityRow) => activityRow.ip));
},
},
strict: process.env.DEV,
});
And in your view:
<template>
<div v-if="isReady">
<maps-geo-ips-card />
</div>
</template>
<script>
import mapsGeoIpsCard from "components/cards/mapsGeoIpsCard";
export default {
name: "dashboard",
components: {
mapsGeoIpsCard,
},
computed: {
activity() {
return this.$store.state.activity;
},
activityIps() {
return this.$store.getters.activityIps;
},
isReady() {
return this.$store.state.isReady;
},
};
</script>
Getting an error “Cannot convert undefined or null to object” on this VueJs Slider. It is working here http://novo.evbox.com/ (Its the first component on the page) . The functionality works but I would like to solve the error in the console. Does anyone have any insights?
Note: I have removed some code for brevity.
<template>
<div id="vue-slider">
<div
id="button-toggle-container"
class="button-toggle-container flex justify-center justify-between mt3 mb4 mx-auto"
>
<button
class="button-toggle"
v-for="(slidePanelKey, mainIndex) in Object.keys(slider)"
:id="slidePanelKey"
:key="mainIndex"
#click="setActivePanel(mainIndex)"
:class="{active: mainIndex == activeButtonIndex}"
>{{ slidePanelKey }}</button>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "slider",
props: {
slide: Object
},
data() {
return {
slider: null,
retrieved: {
slider: false
}
}
},
mounted: function () {
// Retrieve slides data.
axios
.get(
"/api/?hash=API-KEY&type=slider" +
"&context=" + this.getContext()
)
.then(response => {this.slider = response.data.data
this.slide = Object.values(this.slider)
})
// eslint-disable-next-line
.catch(error => console.log(error))
},
},
};
</script>
.then(response => {
if(response.data && typeof response.data.data === 'object') {
this.slide = Object.values(response.data.data)
}
})
I think this will solve the issue
.then(response => {
if(response.data && response.data.data) {
this.slider = response.data.data
this.slide = Object.values(this.slider)
}
})
I am trying to get an array out of an axios call:
so that I can access the data for a component. I'm aware that i could use some thing like
return {
a: []
}
}
getTags(index) {
axios.get('http://localhost:8080/user/tag?imageIndex=' + index)
.then(response => {
this.a = response.data
})
},
But the Problem is, that i have for each image one array and the number of images are dynamic. So i would like to just give a array back
Is there a opportunity to do as I want?
I could live with generating all the arrays in data() if there is a way to do that dynamically. Or can axios return it?
Here my Code that does not work:
<template>
<div id="SingleFile">
<button
id="refreshbtn"
class="btn btn-primary btn-margin"
#click="updateFileList">
refresh
</button>
<gallery :images="images" :index="index" #close="index = null"></gallery>
<div
:key="imageIndex"
:style="{ backgroundImage: 'url(' + image + ')', width: '300px', height: '200px' }"
#click="index = imageIndex"
class="image"
v-for="(image, imageIndex) in images"
>
<div>
<vue-tags-input
v-model="tag"
:tags="getTags(imageIndex)"
#tags-changed="newTags => tags = newTags"
/>
</div>
</div>
<div class="upload">
<upload-image url="http://localhost:8080/user" name="files" max_files="100"></upload-image>
</div>
</div>
</template>
<script>
import VueGallery from 'vue-gallery';
import axios from 'axios';
import auth from './../service/AuthService'
import router from './../router'
import UploadImage from 'vue-upload-image';
import VueTagsInput from '#johmun/vue-tags-input';
export default {
components: {
'gallery': VueGallery,
'upload-image': UploadImage,
VueTagsInput
},
data() {
return {
images: [],
index: null,
tag: '',
};
},
created() {
this.checkAuth()
},
methods: {
checkAuth() {
if (auth.isAuthenticated()) {
this.updateFileList()
} else {
router.replace('/')
}
},
updateFileList() {
axios.get('http://localhost:8080/user')
.then(response => {
this.images = response.data
})
},
getTags(index) {
return axios.get('http://localhost:8080/user/tag?imageIndex=' + index)
.then(response => {
return response.data
})
},
},
};
</script>
The best way is to return the data using axios in mounted hook or by calling a method after firing some event :
mounted(){
//return all your images using valid url
axios.get('http://localhost:8080/user/tag')
.then(response => {
this.a = response.data
})
}
and your method should be like as :
methods:{
getTags(i){
return this.a[i];
}
}