Vue.js - Return array from axios - javascript

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];
}
}

Related

The right way to draw a Map when data is ready

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 Vue component

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)
}
})

Computed property is not defined on the instance but reference during render

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>

React inline style with a variable from a function

I am trying to display the product getting the size it should be from a Json database. I am new to react so have tried a few ways and this is what I have been able to do.
I tried making a function (FontSize) that creates a variable (percentage) with the value I want before and then tried calling the function in the render in the tag with the product. I am getting no errors but the size of the paragraph tag is not changing.
This is my component.
import React, { Component } from 'react';
import { Loading } from './LoadingComponent';
const API = 'http://localhost:3000/products';
class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
isLoading: false,
error: null,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(API)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ products: data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
FontSize = () => {
const { products } = this.state;
var percentage = products.size + 'px';
return percentage;
}
render() {
const Prods = () => {
return (
<div>
<div className="row">
<button onClick={this.sortPrice}>sort by price lower to higher</button>
<button onClick={this.sortSize}>sort by size small to big</button>
<button onClick={this.sortId}>sort by Id</button>
</div>
{products.map(product =>
<div className="row">
<div className="col-3">
<p> Price: ${(product.price/100).toFixed(2)}</p>
</div>
<div className="col-3">
<p style={{fontSize : this.FontSize()}} > {product.face}</p>
</div>
<div className="col-3">
<p>Date: {product.date} {this.time_ago}</p>
</div>
</div>
)}
<p>"~END OF CATALOG~"</p>
</div>
);
};
const { products, isLoading, error } = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <Loading />;
}
return (
<Prods />
);
}
}
export default Products;
What I get in the console using console.log(products)
I think you need quotes around your style value to work properly.
With concatenation it would look like this for Example:
style={{gridTemplateRows: "repeat(" + artist.gallery_images.length + ", 100px)"}}
Another general example from React:
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}

React app: Images from firebase are not shown on a page

I want to display images on a page that come from firebase. I can successfully get urls from the storage but when I use them in image as a src, they (images) are not shown.
As you can see from the image above, image tags are blank. I don't know what is the problem. Could suggest me a way out. My code is the following:
import React, { Component } from 'react';
import { firebase } from '../../../firebase';
import AdminLayout from '../../../Hoc/AdminLayout';
import { firebasePhotos } from '../../../firebase';
import { firebaseLooper, reverseArray } from '../../ui/misc';
import { css } from 'react-emotion';
import { BarLoader } from 'react-spinners';
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
class Adminphoto extends Component {
state = {
isLoading: true,
photos: [],
marginTop: '40px',
successForm: ''
};
componentDidMount() {
firebasePhotos.once('value').then(snapshot => {
const photos = firebaseLooper(snapshot);
this.setState({
isLoading: false,
marginTop: '0px',
photos: reverseArray(photos)
});
});
}
getURL = filename => {
//console.log(filename);
firebase
.storage()
.ref('photos')
.child(filename)
.getDownloadURL()
.then(url => {
console.log('url =>' + url);
return url;
});
};
successForm(message) {
this.setState({
successForm: message
});
setTimeout(() => {
this.setState({
formSuccess: ''
});
}, 2000);
}
deleteItem(event, photo) {
event.preventDefault();
confirmAlert({
title: 'Confirm to submit',
message: 'Are you sure to do this.',
buttons: [
{
label: 'Yes',
onClick: () => {
firebasePhotos
.child(photo.id)
.remove()
.then(() => {
this.successForm('Removed successfully');
this.props.history.push('/admin_photo');
});
}
},
{
label: 'No',
onClick: () => {
return false;
}
}
]
});
}
render() {
console.log(this.state.photos);
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
return (
<AdminLayout>
<React.Fragment>
<div
className="has-text-centered"
style={{ marginTop: this.state.marginTop }}
>
{this.state.isLoading ? (
<BarLoader
className={override}
sizeUnit={'px'}
size={50}
width={100}
height={4}
color={'#2D7969'}
loading={this.state.loading}
/>
) : (
''
)}
</div>
<div className="columns">
{this.state.photos
? this.state.photos.map((photo, i) => (
<div key={i} className="column">
<img src={this.getURL(photo.image)} />
</div>
))
: null}
</div>
</React.Fragment>
</AdminLayout>
);
}
}
export default Adminphoto;
changes i made:
1. this.state.photos to be the img src
2. this.getURL() is called in componentDidMount()
3. <img> gets src directly from state
componentDidMount() {
firebasePhotos.once('value').then(snapshot => {
const photos = firebaseLooper(snapshot);
reverseArray(photos).map(photo => {
this.getURL(photo.image)
})
this.setState({
isLoading: false,
marginTop: '0px',
})
});
}
getURL = filename => {
//console.log(filename);
firebase
.storage()
.ref('photos')
.child(filename)
.getDownloadURL()
.then(url => {
this.setState({ photos: [...this.state.photos, url] })
});
};
render() {
...
this.state.photos.map((photo, i) => (
<div key={i} className="column">
<img src={photo} />
</div>
))
...
hope it works, lemme know if im not really clear at explaining
One way to do this is to save url of image in the firebase realtime database and then get them from database and save them in the state.
You can manually save the urls in the database while uploading them or write cloud function triggers which will save urls in the database, every time image is uploaded in the firebase storage.

Categories