Fetch each object from API data using vue and axios - vue.js

This is my script that calls axios and fetch data as posts
<script>
import axios from 'axios'
export default {
name: 'App',
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
},
data() {
return {
posts: null
}
},
};
</script>
My code on view that tries to fetch data as posts from the script above
<template>
<div id="app">
<ul>
<li v-for="post in posts" v-text="post.createdAt"></li>
</ul>
<div>
</template>
SAMPLE data fetched from API URL look like this
POSTS OBJECT VARIABLES
I am able to fetch API DATA in console log as an array but when I call one object from array which is createdAT, v-text = "post.createdAt" does not print/fetch list of createdAt date list.

Just solved it following this document USING AXIOS TO CONSUME API here is the link for that https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html .
Above code that I have posted works fine. Problem was on my API URL which was nested inside data[data[object]]. So the way I called data from that API
from this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
}
to this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data.data)
}

posts isn't reactive because the default value is null, make it an empty array, or use Vue.set instead:
Array:
posts: []
Vue.set:
.then(response => Vue.set(this, 'posts', response.data))
Edit
In response to the comments below:
You must import Vue from 'vue' to resolve the Vue is not defined error.
Regarding your v-for-key needing to be unique, you need to define a unique key on v-for elements. You can just use JSON.stringify(post):
<li v-for="post in posts" v-text="post.createdAt" :key="JSON.stringify(post)"></li>

Related

VUE3 pass data between files

In Home.vue I get data from db.json and store it into jobs: [] array.
export default {
name: 'Home',
data() {
return {
jobs: [],
}
},
components: {
},
mounted() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
}
}
Also in Home.vue I show this data, but only in a short list with:
v-for="job in jobs.slice(0, 5)"
In AllJobs.vue I want to show the full data from db.json and in AddJob.vue I will make a form to be able to add data to db.json.
In App.vue I have the router-links:
<template>
<div class="container">
<div class="navigation">
<h1 class="title">{{ $route.name }}</h1>
<nav>
<router-link :to="{ name: 'Latest open positions' }">Home</router-link>
<router-link :to="{ name: 'All open positions' }">Jobs</router-link>
<router-link :to="{ name: 'Add a new job' }">Dashboard</router-link>
</nav>
</div>
<router-view/>
</div>
</template>
How I pass data from Home.vue into AllJobs.vue?
Should I get another fetch method into AllJobs.vue to get data?
Should I get data into App.vue and then pass it into files that I need?
What is the best approach?
When it comes to handling API requests and sharing data between components, what you need is some state management solution like pinia.
You can fetch and save your data in a store and then use it in any component:
jobs.js
export const useJobsStore = defineStore('jobs', {
state: () => ({ jobs: [] }),
actions: {
fetchJobs() {
fetch("http://localhost:3000/jobs")
.then(res => res.json())
.then(data => this.jobs = data)
.catch(err => console.log(err.message))
},
},
})
App.vue
import { mapActions } from 'pinia
import { useJobsStore } from './jobs.js'
export default {
methods: {
...mapActions(useJobsStore, ['fetchJobs'])
},
mounted() {
this.fetchJobs()
}
}
Home.vue and AllJobs.vue
import { mapState } from 'pinia'
import { useJobsStore } from './jobs.js'
export default {
computed: {
// this makes this.jobs available in script and template
...mapState(useJobsStore, ['jobs'])
}
}
One thing which is debatable is where to call fetchJobs action
In App.vue or main.js - this will fetch data as soon as you open the app, but can be unnecessary if the page you visit doesn't even use the data.
In each page that uses the data - solves the previous problem, but fetches the same data multiple times.
In each page that uses the data (with caching) - you can modify fetchJobs to make a request only if the data haven't been fetched already. This way the app will fetch the data as soon as you visit some page which uses it. And if you visit another page, it will use the cached value instead of making a request
There isn't a singe best approach, which one to pick depends on your needs

Vue 3 display fetch data v-for

So, I'm creating a Pokemon application and I would like to display the pokemon names using the api : https://pokeapi.co/api/v2/pokemon/.
I'm doing a fetch request on the api and then display the pokemon names in my template. I have 0 problem when I try to display only 1 pokemon but I have this error when I try to display all my pokemons using v-for.
Do you have any idea why I meet this error ?
<template>
<p class="dark:text-white"> {{pokemons[0].name}} </p> //working
<div v-for="(pokemon, index) in pokemons" :key="'poke'+index"> //not working...
{{ pokemon.name }}
</div>
</template>
<script>
const apiURL = "https://pokeapi.co/api/v2/pokemon/"
export default {
data(){
return{
nextURL:"",
pokemons: [],
};
},
created(){
this.fetchPokemons();
},
methods:{
fetchPokemons(){
fetch(apiURL)
.then( (resp) => {
if(resp.status === 200){
return resp.json();
}
})
.then( (data) => {
console.log(data.results)
// data.results.forEach(pokemon => {
// this.pokemons.push(pokemon)
// });
// this.nextURL = data.next;
this.pokemons = data.results;
console.log(this.pokemons);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style>
</style>
I've just pasted your code into a Code Pen and removed the working/not working comments and the code runs and shows the names.
Maybe the problem is in the parent component where this component is mounted, or the assignment of the :key attribute
try :key="'poke'+index.toString()", but I'm pretty sure js handels string integer concats quiet well.
Which version of vuejs do you use?
Edit from comments:
The parent component with the name PokemonListVue imported the posted component as PokemonListVue which resulted in a naming conflict. Renaming either one of those solves the issue.
In the error message posted, in line 3 it says at formatComponentName this is a good hint.

Dynamic list not rendered

I'm a beginner in VueJS so any help would be appreciated.
So I'm trying to display a list from my API. My API is working fine as I have tested it in Postman but when I try to create a list based on the response of my API, the list is not displayed. What am I doing wrong?
Here is my html:
<div id="tabs">
<ul>
<li v-for="user in users">
{{ user.userid }}
</li>
</ul>
</div>
And here is my js:
var tabs = new Vue({
el: '#tabs',
data: {
users: []
},
mounted: function(){
this.getAllUsers()
},
methods: {
getAllUsers: function(){
axios.get('<?php echo base_url(); ?>users/all_users')
.then(function(response){
this.users = response.data
console.log(this.users)
})
}
}
})
Here is the screenshot of the console, the data are just for testing.
value of users after getting response from API
In your axios "then" method, you write:
.then(function(response){
this.users = response.data;
});
When using function keyword to declare a function, it creates its own context and therefore this value of its parent scope is not passed down to the function.
In Vue's case, this in the child function is not the intended Vue instance.
To solve this, simply pass the parent's this value to the callback, or use arrow functions.
Passing down using .bind
.then(function(response){
this.users = response.data;
}.bind(this));
Using arrow function syntax
.then(response => {
this.users = response.data;
});
<?php echo base_url(); ?>
I don't think this will be rendered in a js file. try hardcoding your baseURL instead.
Example:
getAllUsers: function(){
axios.get('http://app.test/users/all_users') // or axios.get('/api/users/all_users') if you're using an api based route for your api
.then(function(response) {
this.users = response.data
console.log(this.users)
})
}

Vue app only renders API data correctly in local dev, not in production build (using vuex, axios, and parcel)

I've got a basic client-side rendered vue app that fetches a json array from an API endpoint and renders data from each array item as a list item in an ordered list.
The data renders as expected when I'm developing locally, using parcel index.pug to spin up my local dev environment. Here's a screenshot of the expected ordered list as shown in my local dev environment at localhost:1234:
However, the data does not render as expected when I build for production (using parcel build index.pug). Here's a screenshot of the unexpected behavior in production mode (which you can see live online at https://errands.netlify.com/):
Notice that the production version knows the fetched API data is an array with a length of 6 (because it renders out 6 empty <li>s), but for some reason the production version does not know the data inside each array object.
The full source code for this app is here, https://github.com/brianzelip/groceries-vue.
The main relevant code (the axios call to the API, which updates the vuex store, then renders out the ordered list) lives in TheGroceryForm.vue, which is also included here:
<template>
<form action="/submit" method="POST">
<ol class="list-reset border rounded">
<li class="item" v-for="item in allGroceryItems" v-bind:key="item._id">
<GroceryFormItemEditLink/>
<GroceryFormItemCheckboxInput :slug="item.slug"/>
<GroceryFormItemCheckboxLabel :slug="item.slug" :name="item.name"/>
<GroceryFormItemQtySelector :slug="item.slug"/>
<GroceryFormItemStoresSelector
:stores="item.stores"
:slug="item.slug"
:defaultStore="item.defaultStore"/>
</li>
</ol>
</form>
</template>
<script>
import axios from 'axios';
import GroceryFormItemEditLink from './GroceryFormItemEditLink.vue';
import GroceryFormItemCheckboxInput from './GroceryFormItemCheckboxInput.vue';
import GroceryFormItemCheckboxLabel from './GroceryFormItemCheckboxLabel.vue';
import GroceryFormItemQtySelector from './GroceryFormItemQtySelector.vue';
import GroceryFormItemStoresSelector from './GroceryFormItemStoresSelector.vue';
export default {
data() {
return {};
},
components: {
GroceryFormItemEditLink,
GroceryFormItemCheckboxInput,
GroceryFormItemCheckboxLabel,
GroceryFormItemQtySelector,
GroceryFormItemStoresSelector
},
computed: {
allGroceryItems() {
return this.$store.state.allGroceryItems;
},
allGroceryItemsCount() {
return this.$store.getters.allGroceryItemsCount;
}
},
getters: {},
created() {
axios
.get('https://groceries-vue-api.glitch.me/get')
.then(res => {
console.log('axio.get worked! res.data =', res.data);
console.log(`typeof res.data = ${typeof res.data}`);
const groceryData = res.data;
console.log('groceryData =', groceryData);
console.log(`typeof groceryData = ${typeof groceryData}`);
const newData = res.data.map(obj => obj);
console.log('newData', newData);
return newData;
})
.then(data => {
console.log('data from second then() =', data);
return data;
})
.then(data => (this.$store.state.allGroceryItems = data))
.catch(error => {
console.log('ERROR! ->', error);
});
}
};
</script>
Why does the data change when I build my app for production? And how can I change this behavior to get the expected outcome?
You should avoid using self closing tags.
https://github.com/vuejs/vue/issues/1036
So your TheGroceryForm.vue will be
<template>
<form action="/submit" method="POST">
<ol class="list-reset border rounded">
<li class="item" v-for="item in allGroceryItems" v-bind:key="item._id">
<GroceryFormItemEditLink></GroceryFormItemEditLink>
<GroceryFormItemCheckboxInput :slug="item.slug"></GroceryFormItemCheckboxInput>
<GroceryFormItemCheckboxLabel :slug="item.slug" :name="item.name"></GroceryFormItemCheckboxLabel>
<GroceryFormItemQtySelector :slug="item.slug"></GroceryFormItemQtySelector>
<GroceryFormItemStoresSelector
:stores="item.stores"
:slug="item.slug"
:defaultStore="item.defaultStore"></GroceryFormItemStoresSelector>
</li>
</ol>
</form>
</template>

VueJS: TypeError: Cannot read property of undefined when Reload

I have a page like this:
<template>
<div class="row flex">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
async asyncData ({ route }) {
let { data } = await axios.get('http://localhost:8000/api/v1/feeds/' + route.params.id + '/')
return {
posts: data
}
}
}
</script>
When I click link with hot reload (router-link), it display well. But when I reload this window, it appear in 1 seconds and disappear then.
Video: http://g.recordit.co/ht0a0K2X81.gif
Error Log:
How can I fix this?
Add a property to your data i.e dataLoaded: false. When your ajax request has finished, set this.dataLoaded = true. On your template add v-if="dataLoaded. This will mean the template data won't render until you're ready.
You could also do v-if="posts" as another way but I generally have a consistent dataLoaded prop available to do this.
Edit: I just looked at your example again and doing something like this would work:
<template>
<div class="row flex" v-if="posts">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
posts: null
}
}
methods:{
loadPosts () {
return axios.get('http://localhost:8000/api/v1/feeds/' + this.$route.params.id + '/')
}
},
created () {
this.loadPosts().then(({data}) => {
this.posts = data
})
}
}
</script>
I've removed the async and just setting posts when the axios request returns it's promise. Then on the template, it's only showing posts is valid.
Edit
You can also use your original code and just add v-if="posts" to the div you have in your template.