VueJS Array Index returns wrong result - javascript

I am creating a poetry app where poetry is fetched using an API call.
I fetch data using axios library and do v-for to populate data. I use the index from v-for to populate the image for each poem respectively.
I display 10 results per page using my own custom pagination. Currently, it's only for next button though.
The problem I am facing is when I navigate to Page 2! As I said earlier, that I use v-for's index to display images, it doesn't actually update the index when I move to the next page. As a result, the images are shown same as of page 1.
Code:
new Vue({
el: '#app',
data: {
proxy: 'https://cors-anywhere.herokuapp.com/',
imageIndex: 0,
pagination: {
start: 0,
end: 10,
resPerPage: 10
},
fetchData: [],
fetchImages: []
},
methods: {
paginate() {
this.pagination.start = this.pagination.start + this.pagination.resPerPage;
this.pagination.end = this.pagination.end + this.pagination.resPerPage;
},
async fetchDatas() {
try {
const res = await axios(`${this.proxy}http://poetrydb.org/author,title/Shakespeare;Sonnet`);
if (res) {
this.fetchData = res.data;
}
} catch (error) {
console.log(error);
}
},
async fetchImagess() {
const key = '9520054-7cf775cfe7a0d903224a0f896';
const perPage = 154;
const proxy = ''
const res = await axios(`${this.proxy}https://pixabay.com/api/?key=${key}&per_page=${perPage}`);
this.fetchImages = res.data.hits;
}
},
mounted() {
this.fetchDatas();
this.fetchImagess();
}
});
<div id="app">
<div v-for="(poetry, index) in fetchData.slice(this.pagination.start, this.pagination.end)">
<div>
<img :src="fetchImages[index].largeImageURL.toLowerCase()" style="max-width: 100%;height: auto;max-height: 320px;">
<div>
<h5>{{ poetry.title }}</h5>
<span v-for="(poetryBody, i) in poetry.lines.slice(0, 5)">
{{ i === 4 ? poetryBody.split(',').join('') + '...' : poetryBody }}
</span>
<br>
Read More
</div>
</div>
</div>
<nav style="padding-top: 3em;">
<button #click="paginate()">Next</button>
</nav>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
JSFiddle: http://jsfiddle.net/sanjaybanjade/vnu654gk/9/
As you can see the images doesn't get updated when I goto Page 2! Please help me fix this!
And please ignore the console errors. I am gonna fix them later.

The quick fix would be to calculate the offset in line 4 to update on pagination:
<img v-bind:src="fetchImages[index + pagination.start].largeImageURL.toLowerCase()" style="max-width: 100%;height: auto;max-height: 320px;">

wrong at this line fetchImages[index].largeImageURL.toLowerCase().
Since you are iterating a sliced array of fetchData, it's index is related to sliced array, not original array. So, you should apply pagination slice to your fetchImages too.

When you run fetchData.slice(), it returns a new object. So if you slice out 10 new pieces of poetry, their indexes are still going to be 0-9, since the returned object only has that many items each time.

Why it's not working is because you only slice fetchData on this line fetchData.slice(this.pagination.start, this.pagination.end) but you don't slice the fetchImages what means fetchImages still is the same array it didn't change, meaning that index 0 is still the same image. Best is if you keep them in sync so I would add a pageData and pageImages array's and every time you change the paging you update both of them. like in a updatePageData method
new Vue ({
el: '#app',
data: {
proxy: 'https://cors-anywhere.herokuapp.com/',
imageIndex: 0,
pagination: {
start: 0,
end: 10,
resPerPage: 10
},
fetchData: [],
fetchImages: [],
pageData: [],
pageImages: []
},
methods: {
paginateNext() {
this.pagination.start = this.pagination.start + this.pagination.resPerPage;
this.pagination.end = this.pagination.end + this.pagination.resPerPage;
this.updatePageData()
},
updatePageData () {
this.pageData = this.fetchData.slice(this.pagination.start, this.pagination.end)
this.pageImages = this.fetchImages.slice(this.pagination.start, this.pagination.end)
},
async fetchDatas() {
try {
const res = await axios(`${this.proxy}http://poetrydb.org/author,title/Shakespeare;Sonnet`);
if(res) {
this.fetchData = res.data;
}
} catch(error) {
console.log(error);
}
},
async fetchImagess() {
const key = '9520054-7cf775cfe7a0d903224a0f896';
const perPage = 154;
const proxy = ''
const res = await axios(`${this.proxy}https://pixabay.com/api/?key=${key}&per_page=${perPage}`);
this.fetchImages = res.data.hits;
}
},
mounted() {
Promise.all([
this.fetchDatas(),
this.fetchImagess()
])
.then(() => this.updatePageData())
}
});
<div id="app">
<div v-for="(poetry, index) in pageData">
<div>
<img :src="pageImages[index].largeImageURL.toLowerCase()" style="max-width: 100%;height: auto;max-height: 320px;">
<div>
<h5>{{ poetry.title }}</h5>
<span v-for="(poetryBody, i) in poetry.lines.slice(0, 5)">
{{ i === 4 ? poetryBody.split(',').join('') + '...' : poetryBody }}
</span>
<br>
Read More
</div>
</div>
</div>
<nav style="padding-top: 3em;">
<button #click="paginateNext()">Next</button>
</nav>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

Related

Problem with Vue.js when it needs to fetch an url (wp-api) on a keyup event

Here's my issue. I created a tool with vue.js and the WordPress API to search through the search endpoints for any keyword and display the result. So far so good, everything is working, except for a bug that I spotted.
Here's the deal:
const websiteurl = 'https://www.aaps.ca'; //yourwebsite or anything really
var vm = new Vue({
el: '#blog-page',
data: {
noData: false,
blogs: [],
page: 0,
search: '',
totalPagesFetch: "",
pageAmp: "&page=",
apiURL: `${websiteurl}/wp-json/wp/v2/posts?per_page=6`,
searchbyid: `${websiteurl}/wp-json/wp/v2/posts?per_page=6&include=`,
searchUrl: `${websiteurl}/wp-json/wp/v2/search?subtype=post&per_page=6&search=`,
},
created: function () {
this.fetchblogs();
},
methods: {
fetchblogs: function () {
let self = this;
self.page = 1;
let url = self.apiURL;
fetch(url)
.then(response => response.json())
.then(data => vm.blogs = data);
},
searchonurl: function () {
let ampersand = "&page=";
searchPagination(1, this, ampersand);
},
}
});
function searchPagination(page, vm, pagen) {
let self = vm;
let searchword = self.search.toLowerCase();
let newsearchbyid = self.searchbyid;
let url;
self.page = page;
url = self.searchUrl + searchword + pagen + self.page;
self.mycat = 'init';
fetch(url)
.then(response => {
self.totalPagesFetch = response.headers.get("X-WP-TotalPages");
return response.json();
})
.then(data => {
let newid = [];
data.forEach(function (item, index) {
newid.push( item.id );
});
if (newid.length == 0) {
return newsearchbyid + '0';
} else {
return newsearchbyid + newid;
}
})
.then(response2 => {
return fetch(response2)
})
.then(function(data2) {
return data2.json();
})
.then(function(response3) {
console.log(response3)
if (response3.length == 0) {
vm.noData = true;
vm.blogs = response3;
} else {
vm.noData = false;
vm.blogs = response3;
}
})
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="lazyblock-blogs testblog" id="blog-page">
<div class="container">
<div class="row controls">
<div class="col-md-12">
<div class="search-blog">
<img height="13" src="" alt="search">
<input id="sb" type="text" v-model="search" #keyup="searchonurl" placeholder="search">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4" v-for="(b, index) in blogs">
<div class="h-100 box" v-cloak>
<img width="100%" v-bind:src=b.featured_image_url>
<a v-bind:href="b.link">
<h3 v-html=b.title.rendered></h3>
</a>
<div v-html=b.excerpt.rendered></div>
<p class="read-more"><a v-bind:href="b.link">read more</a></p>
</div>
</div>
<div class="no-data" v-if="noData">
<div class="h-100">
No post
</div>
</div>
</div>
</div>
</div>
I'm using a keyup event which is causing me some problems because it works, but in same cases, for example, if the user is very fast to type characters and then suddenly he wants to delete the word and start again, the response for the API has some sort of lag.
The problem is that I guess that the Vue framework is very responsive (I create a variable call search that will update immediately) but the API call in the network is not (please check my image here):
This first image appears if I type lll very fast, the third result will return nothing so it is an empty array, but if I will delete it immediately, it will return an url like that: https://www.aaps.ca//wp-json/wp/v2/search?subtype=post&per_page=6&search=&page=1 which in turn should return 6 results (as a default status).
The problem is that the network request won't return the last request but it gets crazy, it flashs and most of the time it returns the previous request (it is also very slow).
Is that a way to fix that?
I tried the delay function:
function sleeper(ms) {
return function(x) {
return new Promise(resolve => setTimeout(() => resolve(x), ms));
};
}
and then I put before the then function:
.then(sleeper(1000))
but the result is the same, delayed by one second (for example)
Any thought?
This is the case for debounced function. Any existing implementation can be used, e.g. Lodash debounce. It needs to be declared once per component instance, i.e. in some lifecycle hook.
That searchPagination accepts this as an argument means that something went wrong with its signature. Since it operates on component instance, it can be just a method and receive correct this context:
methods: {
searchPagination(page, pagen) {
var vm = this;
...
},
_rawsearchonurl() {
let ampersand = "&page=";
this.searchPagination(1, ampersand);
}
},
created() {
this.searchonurl = debounce(this._rawsearchonurl, 500);
...
}
You could use debounce, no call will leave until the user stop typing in the amount of time you chose
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
}
// in your "methods" (I put 1000ms of delay) :
searchonurl: function () {
let ampersand = "&page=";
debounce(searchPagination, 1000)(1, this, ampersand);
}
One of best ways is to use Debounce which is mentioned in this topic
Or use a function and combine it with watch. Follow these lines:
In mounted or created make an interval with any peroid you like (300 etc.) define a variable in data() and name it something like searched_value. In interval function check the value of your input and saerch_value, if they were not equal (===) then replace search_value with input value. Now you have to watch search_value. When it changed you call your api.
I use this method and works fine for me. Also it`s managable and everything is in your hand to config and modify.
===== UPDATE: =====
A simple code to do what I said above
<template>
<div>
<input type="search" v-model="search_key">
</div> </template>
<script> export default {
name: "SearchByApi",
data() {
return {
search_key: null,
searched_item: null,
loading: false,
debounceTime: 300,
}
},
created() {
this.checkTime()
const self = this
setInterval(function() {
self.checkTime()
}, this.debounceTime);
},
watch: {
searched_item() {
this.loadApi()
}
},
methods: {
checkTime() {
if (this.searched_item !== this.search_key && !this.loading) {
this.searched_item = this.search_key
}
},
loadApi() {
if (!this.loading && this.searched_item?.length > 0) {
this.loading = true
const api_url = 'http://api.yourdomain.com'
axios(api_url, {search: this.searched_item}).then(res => {
// whatever you want to do when SUCCESS
}).catch(err => {
// whatever you want to do when ERROR
}).then(res => {
this.loading = false
})
}
}
}
}
</script>

Append Each WebSocket Message to div - VUE3

I'm trying to render each trade from Binances Websocket Stream in my VUE3 component. I can render 1 line and that line keeps updating, however this is not what i'm trying to achieve. Many thanks for all suggests / solutions.
<template>
<div>
<div v-for="data in tradeDataList" :key="data.id">
<div>
{{ data }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data: () => {
return {
connection: null,
tradeDataList: [],
}
},
created() {
this.getTradeStream();
},
methods: {
getTradeStream() {
console.log("Starting connection to WebSocket Server");
this.connection = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt#trade");
this.connection.addEventListener("message", (event) => {
let tradeDataString = event.data;
this.tradeDataList = [];
let parsedData = JSON.parse(tradeDataString);
this.tradeDataList = parsedData;
console.log(this.tradeDataList);
});
this.connection.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
}
}
}
</script>
i have tried v-for looping through this.tradeDataList - I was expecting a list with one trade per line. What I saw was 1 line that constantly updates rather than making a new line.
Rather than, clearing out this.tradeDataList = []; and replaing the item this.tradeDataList = parsedData; push the item to the array. Optionally remove old items with splice etc
new Vue({
el: '#app',
data: () => {
return {
connection: null,
tradeDataList: [],
}
},
created() {
this.getTradeStream();
},
methods: {
getTradeStream() {
console.log("Starting connection to WebSocket Server");
this.connection = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt#trade");
this.connection.addEventListener("message", (event) => {
let tradeDataString = event.data;
let parsedData = JSON.parse(tradeDataString);
// push new item to array
this.tradeDataList.push(parsedData);
// keep only last 10
this.tradeDataList = this.tradeDataList.slice(Math.max(this.tradeDataList.length - 10, 0))
});
this.connection.onopen = function(event) {
//console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<div id="app">
<div>
<div v-for="data in tradeDataList" :key="data.id">
<div>
{{ data.t }} - {{ data.p }}
</div>
</div>
</div>
</div>

Display Vue Computed Array of Objects in HTML

I am having trouble displaying an array of objects from Vue that is fetched from an express server using fetch(); The fetching of the data works but I am not sure as how to display it in html. Below is the Vue code that is successfully fetching the JSON from Express.
computed: {
async fetchData() {
fetch('http://localhost:4000/lessons').then(
function (response) {
response.json().then(
function (json) {
this.lessons = json;
console.log(this.lessons)
});
})
},
}
The console.log successfully displays the fetched array of objects but it is not being displayed in HTML. Below is the HTML code that is not displaying the fetched array of objects.
<div v-for="lesson in fetchData" class="card">
<h2 v-text ="lesson.subject"></h2>
<figure>
<img v-bind:src="lesson.image">
</figure>
<p>Location: {{lesson.location}}</p>
<p>Price: £{{lesson.price}}</p>
<p>Description: {{lesson.description}}</p>
<p>Maximum Class Size: {{lesson.maximumSpaces}} People</p>
</div>
How will I be able to display the array of objects in the HTML file? Thanks for your time.
There are a few problems: 1) Computeds are not async. 2) The template is not async, so you could not call even an async method that way. 3) Your fetch callback function should be an arrow function or it injects its own this and blocks the data setting. 4) Use a :key with v-for. Here is a proper pattern, use a method to fetch the data:
methods: {
async fetchData() {
const response = await fetch('http://localhost:4000/lessons');
this.lessons = await response.json();
}
}
You can call it in the created or mounted lifecycle hook, or somewhere else:
data: () => ({
lessons: []
}),
created() {
this.fetchData()
}
Then iterate over the data:
<div v-for="(lesson, index) in lessons" class="card" :key="index">
...
</div>
Here is a demo:
new Vue({
el: "#app",
data: () => ({
lessons: []
}),
created() {
this.fetchData()
},
methods: {
async fetchData() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
this.lessons = await response.json();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(lesson, index) in lessons" :key="index">
{{ lesson }}
</div>
</div>

How to display values of an associative array with vue.js?

I have an array which is:
And I want to make a foreach loop and list all key's & script_content's to the view.
My vue components mounted method:
mounted() {
this.loading = true;
axios.get('/app/json-ld/json-ld-settings')
.then(res => {
let data = res.data;
console.log(data.scripts);
this.key = data.scripts[0]['key'];
this.scriptContent = data.scripts[0]['script_content'];
})
.catch(error => {
this.loading = false;
this.$notify({
group: 'notify',
type: 'error',
text: 'Something happened! Please refresh the page and try again or contact support!',
});
});
},
component data:
data: () => ({
errors: {},
key: [],
scriptContent: [],
I am able to display the values of the first array, but don't know how to make a foreach loop in an associative array.
HTML:
<div class="py-3 d-flex flex-row justify-content-end align-content-end">
<div class="pr-2">
<h5>Key</h5>
<span>{{key}}</span>
</div>
<div class="pl-2">
<h5>Script content</h5>
<span>{{scriptContent}}</span>
</div>
</div>
The goal is to list all key's and script_content's in a HTML list or a div.
Any help will be appriciated.
You can just use codes below:
data() {
return {
keys: [],
contents: [],
}
}
...
for (let index in data) {
this.keys.push(data[index].key);
this.contents.push(data[index].script_content);
}
...
Then you can use v-for in html codes to use keys and contents.
You should store all scripts into the data, not just data.scripts[0], and then iterate over them in the template using v-for directive. Here is a couple of good examples:
https://v2.vuejs.org/v2/guide/list.html

Vuejs set method return to template

I'm new to Vue and I'm stuck at the moment. For the practice I'm making an app for episode checklist for series. The first part of the app searches series and add one of them to a database. Result for the search gives me a result like this: https://i.stack.imgur.com/QuOfc.png
Heres my code with template and script:
<template>
<div class="series">
<ul>
<li v-for="item in series" :key="item.id">
<img :src="image_url+item.poster_path"/>
<div class="info">
{{item.name}}
<br/>
<h5>{{item.id}}</h5>
Start Date: {{item.first_air_date}}
<br/>
{{getEpisodeNumber(item.id)}}
<br/>
{{getSeasonNumber(item.id)}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "series",
props: ["series"],
data() {
return {
image_url: "https://image.tmdb.org/t/p/w500",
api_key: {-api key-},
episode_url: "https://api.themoviedb.org/3/tv/",
}
},
methods: {
async getEpisodeNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_episodes })
return await json
},
async getSeasonNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_seasons })
return await json;
}
},
}
</script>
Methods should return to me a number but they return an object, probably promise object. But when I try to console.log the data in the methods they print a value(int). I need reach this value but I'm stuck. I tried to sort of thinks but it fails every time.
I just create a new component called show and pass item.id to this component. In show component, I use another fetch() to get show data again and now it works like I want.

Categories

Resources