Im kind of an Beginner within Vuejs. Im Creating a Site which shows content that is loaded from the Backend into the Frontend. Therfore, I use Axios to connect to the API with this code:
contentList: [],
};
const mutations = {
setContent (state) {
axios
.get("http://backendapi/content")
.then(res => {
const data = res.data;
for (let key in data) {
const object = data[key];
state.contentList.push(object)
}
});
}
};
const actions = {
initContent: ({commit}) =>{
commit('setContent');
}
};
and on my Page i load the Contentlist when mounted:
mounted() {
this.$store.dispatch('initContent');
this.content = this.$store.getters.contentList
}
But the Problem is, every Time i go to another Page and back to this Page, the Content is loaded again into the contentList and everithing ist doubled.
Can someone explain, how to write this in "good Code" and avoiding loading everything double?
Thank you
You can check if already have the content on your list before making the request.
setContent (state) {
if (state.contentList.length == 0){
axios
.get("http://backendapi/content")
.then(res => {
const data = res.data;
for (let key in data) {
const object = data[key];
state.contentList.push(object)
}
});
}
}
or if you want to update each time just make sure the variable is reset each time.
axios
.get("http://backendapi/content")
.then(res => {
const data = res.data;
let contentList = [];
for (let key in data) {
const object = data[key];
contentList.push(object);
}
state.contentList = contentList;
});
Just check whether the content is already loaded before doing an axis call. Also the action is meant to execute the axios call:
const mutations = {
setContent (state, data) {
state.contentList = data
}
};
const actions = {
async initContent: ({commit, state}) =>{
if (state.contentList.length === 0) {
try {
let result = []
let response = await axios.get("http://backendapi/content")
for (let key in response.data) {
result.push(response.data[key])
}
commit('setContent', result);
} catch (error) {
// something went wrong
}
}
}
};
Related
I'm a new programmer and very new to firebase and I'm trying to get the current user files info to display on the screen, it seems that my problem is that I can get the URL and the metadata separately, how do I combine them? how can I take everything at once?
I need to show the file name, date, time, link to download.
const getUserFiles = async () => {
if (!userUID) {
return null;
}
let listRef = storageRef.child(userUID);
listRef.listAll().then(res => {
// res.prefixes.forEach((item) => {
// });
res.items.forEach(item => {
item.getMetadata().then(item => {
var file = {
name: item.name.toString(),
timeCreated: item.timeCreated.toString(),
link: '',
};
myFiles.push(file);
});
});
res.items.forEach(item => {
let counter = 0;
item.getDownloadURL().then(url => {
myFiles[counter].link = url.toString();
});
});
});
console.log(myFiles);
};
the current method don't work! and notice that the userUID its only the uid without the user (local state)
Thanks!
The problem is with the asynchronous calls. You're making an async call in forEach and forEach expects a synchronous function.
You can change the logic to use for-of instead.
See below:
const getUserFiles = async () => {
if (!userUID) {
return null;
}
let listRef = storageRef.child(userUID);
const res = await listRef.listAll();
for (const itemRef of res.items) {
const itemMetadata = await itemRef.getMetadata();
const url = await itemRef.getDownloadUrl();
var file = {
name: itemMetadata.name.toString(),
timeCreated: itemMetadata.timeCreated.toString(),
link: url,
};
myFiles.push(file);
}
console.log(myFiles);
}
The getURL() function creates an array of scraped URLs from the original URL. getSubURL() then loops through that array and scrapes all of those pages' URLs. Currently, this code outputs just fine to the console, but I don't know how to wait for my data to resolve so I can push all gathered data to a single array. Currently, when I try and return sites and then push to array, it only pushes the last value. I believe it's a promise.all(map) situation, but I don't know how to write one correctly without getting an error. Ideally, my completed scrape could be called in another function. Please take a look if you can
const cheerio = require('cheerio');
const axios = require('axios');
let URL = 'https://toscrape.com';
const getURLS = async () => {
try {
const res = await axios.get(URL);
const data = res.data;
const $ = cheerio.load(data);
const urlQueue = [];
$("a[href^='http']").each((i, elem) => {
const link = $(elem).attr('href');
if (urlQueue.indexOf(link) === -1) {
urlQueue.push(link);
}
});
return urlQueue;
} catch (err) {
console.log(`Error fetching and parsing data: `, err);
}
};
const getSubURLs = async () => {
let urls = await getURLS();
try {
//loop through each url in array
for (const url of urls) {
//fetch all html from the current url
const res = await axios.get(url);
const data = res.data;
const $ = cheerio.load(data);
//create object and push that url into that object
let sites = {};
sites.url = url;
let links = [];
//scrape all links and save in links array
$("a[href^='/']").each((i, elem) => {
const link = $(elem).attr('href');
if (links.indexOf(link) === -1) {
links.push(link);
}
//save scraped data in object
sites.links = links;
});
// returns list of {url:'url', links:[link1,link2,link3]}
console.log(sites);
}
} catch (err) {
console.log(`Error fetching and parsing data: `, err);
}
};
Don't think this is a Promise related issue at heart.
You'll need to collect your sites into an array that is initialized outside the loop. Then when getSubURLs() resolves, it will resolve to your array:
const getSubURLs = async() => {
let urls = await getURLS();
let siteList = [];
try {
for (const url of urls) {
// :
// :
// :
siteList.push(sites);
}
} catch (err) {
console.log(`Error fetching and parsing data: `, err);
}
return siteList; // array of objects
};
getSubURLs().then(console.log);
I tried to make my web-crawler to have a loop to crawl the webpage from 1 to around 500. But the result does not include any directed one but to return an only void array.
This code is based on cheerio, jQuery, and axios. JavaScript.
const axios = require("axios");
const cheerio = require("cheerio");
const log = console.log;
const getHtml = async() => {
var i=0
while (i<493){
try {
return await axios.get("https://playentry.org/ds#!/qna?sort=created&rows=20&page="+i);
} catch (error) {
console.error(error);
}
}
};
getHtml()
.then(html => {
let ulList = [];
const $ = cheerio.load(html.data);
const $bodyList = $("div.discussContentWrapper div.discussListWrapper table.discussList").children("tr.discussRow");
$bodyList.each(function(i, elem){
ulList[i] = {
title:$(this).find('td.discussTitle div.discussTitleWrapper'),
writer:$(this).find('td.discussTitle td.discussViewCount'),
viewcount:$(this).find('td.discussTitle td.discussViewCount'),
likecount:$(this).find('td.discussTitle div.discussLikeCount'),
date:$(this).find('td.discussTitle td.discussDate'),
};
});
const data = ulList.filter(n => n.title);
return data;
})
.then(res => log(res));
The output is '''[]''' or '''[ [] ]''' with no real outputs.
Thanks for your help in advance.
I am trying to automatically scroll to the bottom of a div that contains a list of elements once a new element has been added.
Since adding and removing elements is done via Axios using an API, I have to wait for a response from the server in order to update my state in Vuex.
This means that, once an element is added in my state, every time I call the "scrollDown" function, the function scrolls to the second last element (due to the asynchronous Axios call not being registered yet).
My question is, how do I wait for the action in Vuex to finish and then call the function in my component to scroll to the bottom of my div?
I tried using watchers, computed properties, sending props, tracking changes from the actual state in Vuex and none of that worked...
// VUEX
const state = {
visitors: [],
url: 'API URL',
errors: []
}
const mutations = {
ADD_VISITOR(state, response) {
const data = response.data;
data.Photos = [];
state.visitors.data.push(data);
},
}
const actions = {
addVisitor: ({ commit }, insertion) => {
axios
.post(state.url + 'api/visitor', {
name: insertion.visitorName
})
.then(response => {
commit('ADD_VISITOR', response);
})
.catch(error => state.errors.push(error.response.data.message));
state.errors = [];
},
}
// MY COMPONENT FROM WHERE THE ACTIONS ARE BEING DISPATCHED
<div ref="scroll" class="visitors-scroll">
<ul v-if="visitors.data && visitors.data.length > 0" class="list-group visitors-panel">
<!-- Displaying appVisitor component and sending data as a prop -->
<app-visitor v-for="visitor in visitors.data" :key="visitor.id" :visitor="visitor"></app-visitor>
</ul>
</div>
methods: {
// Function that dispatches the "addVisitor" action to add a new visitor to the database
newVisitor() {
const insertion = {
visitorName: this.name
};
if (insertion.visitorName.trim() == "") {
this.errors.push("Enter a valid name!");
} else {
this.$store.dispatch("addVisitor", insertion);
this.name = "";
}
this.errors = [];
this.scrollDown(); // I WANT TO CALL THIS FUNCTION WHEN AXIOS CALL IS FINISHED AND MUTATION IN VUEX IS COMPLETED
},
scrollDown() {
this.$refs.scroll.scrollTop = this.$refs.scroll.scrollHeight;
}
},
Any help is appreciated!
In vuex dispatched action returns a Promise. In case of your code its empty Promise, because there is nothing to return. You need to return/pass your axios Promise and then wait for it in your component. Look at this fixed code:
// VUEX
const state = {
visitors: [],
url: 'API URL',
errors: []
}
const mutations = {
ADD_VISITOR(state, response) {
const data = response.data;
data.Photos = [];
state.visitors.data.push(data);
},
}
const actions = {
addVisitor: ({ commit }, insertion) => {
return axios
.post(state.url + 'api/visitor', {
name: insertion.visitorName
})
.then(response => {
commit('ADD_VISITOR', response);
})
.catch(error => state.errors.push(error.response.data.message));
state.errors = [];
},
}
// MY COMPONENT FROM WHERE THE ACTIONS ARE BEING DISPATCHED
<div ref="scroll" class="visitors-scroll">
<ul v-if="visitors.data && visitors.data.length > 0" class="list-group visitors-panel">
<!-- Displaying appVisitor component and sending data as a prop -->
<app-visitor v-for="visitor in visitors.data" :key="visitor.id" :visitor="visitor"></app-visitor>
</ul>
</div>
methods: {
// Function that dispatches the "addVisitor" action to add a new visitor to the database
newVisitor() {
const insertion = {
visitorName: this.name
};
if (insertion.visitorName.trim() == "") {
this.errors.push("Enter a valid name!");
} else {
this.$store.dispatch("addVisitor", insertion)
.then(() => {
this.scrollDown();
})
this.name = "";
}
this.errors = [];
},
scrollDown() {
this.$refs.scroll.scrollTop = this.$refs.scroll.scrollHeight;
}
},
You could try using the async/await syntax.
This means when it will wait until this.$store.dispatch("addVisitor", insertion) is resolved, that means until response from the API is there, the next lines of code will not be executed.
methods: {
// Function that dispatches the "addVisitor" action to add a new visitor to the database
async newVisitor() {
const insertion = {
visitorName: this.name
};
if (insertion.visitorName.trim() == "") {
this.errors.push("Enter a valid name!");
} else {
await this.$store.dispatch("addVisitor", insertion);
this.name = "";
}
this.errors = [];
this.scrollDown();
},
scrollDown() {
this.$refs.scroll.scrollTop = this.$refs.scroll.scrollHeight;
}
}
Edit: And in your Vueux action, make sure to add a return statement.
const actions = {
addVisitor: ({ commit }, insertion) => {
return axios
.post(state.url + 'api/visitor', {
name: insertion.visitorName
})
.then(response => {
commit('ADD_VISITOR', response);
})
.catch(error => state.errors.push(error.response.data.message));
state.errors = [];
},
}
I'm doing a project that fetch different types of data from SWAPI API (people, planets, etc.) using react but I have an issue with multiple Ajax request.
The problem is when I quickly request from 2 different URL for example, 'species' and 'people', and my last request is 'species' but the load time of 'people' is longer, I will get 'people' instead.
What I want is to get the data of the last clicked request, if that make sense.
How do I achieve that? All the solution I found from Google is using jQuery.
Here's a slice of my code in src/app.js (root element) :
constructor(){
super();
this.state = {
searchfield: '',
data: [],
active: 'people'
}
}
componentDidMount() {
this.getData();
}
componentDidUpdate(prevProps, prevState) {
if(this.state.active !== prevState.active) {
this.getData();
}
}
getData = async function() {
console.log(this.state.active);
this.setState({ data: [] });
let resp = await fetch(`https://swapi.co/api/${this.state.active}/`);
let data = await resp.json();
let results = data.results;
if(data.next !== null) {
do {
let nextResp = await fetch(data.next);
data = await nextResp.json();
let nextResults = data.results
results.push(nextResults);
results = results.reduce(function (a, b) { return a.concat(b) }, []);
} while (data.next);
}
this.setState({ data: results});
}
categoryChange = (e) => {
this.setState({ active: e.target.getAttribute('data-category') });
}
render() {
return (
<Header searchChange={this.searchChange} categoryChange={this.categoryChange}/>
);
}
I made a gif of the problem here.
Sorry for the bad formatting, I'm writing this on my phone.
You have to store your requests somewhere and to abandon old ones by making only one request active. Something like:
getData = async function() {
console.log(this.state.active);
this.setState({ data: [] });
// my code starts here
if (this.controller) { controller.abort() }
this.controller = new AbortController();
var signal = controller.signal;
let resp = await fetch(`https://swapi.co/api/${this.state.active}/`, { signal });
let data = await resp.json();
let results = data.results;
if(data.next !== null) {
do {
let nextResp = await fetch(data.next);
data = await nextResp.json();
let nextResults = data.results
results.push(nextResults);
results = results.reduce(function (a, b) { return a.concat(b) }, []);
} while (data.next);
}
this.setState({ data: results});
}