Vue template won't render if array in data function is empty - javascript

I'm having a problem with a Vue template where no elements on the page will not render unless an array declared on data is already populated.
The problem is that the data is only populated after an API call made by submitting a form.
The browser console reads Error in render: "TypeError: Cannot read property 'response' of undefined"
If I comment out the {{classes.data.response}} the form displays but will not otherwise.
Here is what the code looks like.
<template>
<div class="container">
<form #submit="getClass">
<input type="text" placeholder="Search..." v-model="class_id">
<button type="submit">Search</button>
</form>
<br>
<div v-if="classes"> <!-- conditionally render if array has results -->
{{classes.data.response}} <!-- form shows when this is commented out -->
</div>
</div>
</template>
The data block
data() {
return {
classes: []
};
},
...
And the methods block
methods: {
...
// Request
axios(config)
.then(response => (this.classes = response))
.catch(function (error) {
console.log('There was an error :' , error);
});
}
}
I'm relatively new to Vue so if anyone can tell me what is going wrong here I'd much appreciate it. Thanks in advance!

this.classes.data.response is not defined
You can try to be more specific when assigning the response to classes. Instead of this.classes = response, do this this.classes = response.data.response. response.data.response is the Array you are looking for, not response.
methods: {
...
// Request
axios(config)
.then(response => (this.classes = response.data.response))
.catch(function (error) {
console.log('There was an error :' , error);
});
}
}
Then in the template just write {{ classes }} instead of {{ classes.data.response }}, also v-if="classes.length > 0" instead of just v-if="classes".
v-if="classes" will always be true
v-if="classes.length > 0" will be true when the Array has more the 0 elements in it
Why
Because of the asynchronous nature of the API request, the moment the form tries to render this.classes will still be the empty Array you defined. Only later, once the API request has finished, this.classes will have the data it needs.

empty arrays are truthy, so v-if="classes" will always be true. use classes.length, as an empty array will result in 0 which is falsy.

maybe yo can do something like
<div v-if="classes.length>0">
{{classes.data.response}}
</div>

Related

fetching json api in vueJs log the results but renders nothing (I'm new with Vue)

I'm new in VueJs, actually coming from react, the problem I'm having is that when i fetch the data with an axios.get the console.log shows a succesfull response. But when I to iterate through the Array with v-for it renders nothing.
It's quite important so it be super appreciated if you can tell me what im doing wrong or you have a suggestion.
heres the code:
<SignUpForm />
<h1>Countries List</h1 >
<div v-for="country in countries" :key="country.name" >
<p>{{country.name}}</p>
</div>
</template>
<script>
import SignUpForm from "#/components/SignUpForm.vue";
import axios from 'axios'
export default{
name: 'SignUpView',
components:{
SignUpForm
},
data(){
return{
countries: []
}
},
async mounted() {
const {data} = await axios.get('https://gist.githubusercontent.com/keeguon/2310008/raw/bdc2ce1c1e3f28f9cab5b4393c7549f38361be4e/countries.json')
console.info(data)
this.countries = data
}
}
</script>
You can see that what I'm actuallt doing is the axios.get in the mounted(){} f(x), later I = the data to my empty Array declared in data(){}. => So I can iterate trough the json response with a v-for in a div.
Heres the picture so you can see:
Observations :
countries list is a string and v-for is work if the input data is an array.
No. of objects in an array is 243 but this 9251 is a count of characters in a string.
Also, if we will try to convert that into a JSON object via JSON.parse() getting SyntaxError: Unexpected token n in JSON at position 6.
new Vue({
el: "#app",
data: {
countries: []
},
methods: {
getData() {
var vm = this;
axios.get("https://gist.githubusercontent.com/keeguon/2310008/raw/bdc2ce1c1e3f28f9cab5b4393c7549f38361be4e/countries.json")
.then(function(response) {
console.log(typeof response.data); <---- string
console.log(response.data.length); <---- actual objects in an array is 243 but this 9251 is a count of characters in a string.
console.log(JSON.parse(response.data)); <---- SyntaxError: Unexpected token n in JSON at position 6
})
.catch(function(error) {
alert(error);
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<div id="app">
<button #click="getData">Click to check Api response</button>
</div>

How would I iterate through pokeAPI to get all subsequent pokemon data?

Hi I'm using Vuejs to get some pokemon data. So I figured out how to retrieve all the pokemon name and their api urls to get more information about them. The issue is I don't know how to take those URLs and access each pokemon's specific data. I tried to increment a variable and concatenate it to the URL to get their data but it didn't work. I also tried to access the data from the api call I already but that also didn't work.
<template>
<div>
<h2>{{subtitle}}</h2>
<div v-for="pokemon in basicInfo" v-bind:key="pokemon.name">
<span>{{ pokemon.name}}</span>
</div>
<!-- Nothing is produced, and I dont get I an error -->
<div v-for="pokemon2 in advInfo" v-bind:key="pokemon2.index">
<span>{{pokemon2}}</span>
</div>
<script>
import axios from "axios";
export default {
data() {
return {
subtitle: "First 150 pokemon",
basicInfo: [],
advInfo:[],
i:0
};
},
methods: {
// trying to increment i
getNext: function(){
this.i=i++;
}
},
mounted() {
axios
// this gets a list of the first 20 pokemon. I can get the pokemon's name and their url
.get("https://pokeapi.co/api/v2/pokemon/")
.then(response => {
this.basicInfo = response.data.results;
});
// here I'm trying to access more specific data on each pokemon by concatenating a number to the url
axios
.get("https://pokeapi.co/api/v2/pokemon/5")
.then(response => {
this.advInfo= response.data.results;
});
}
};
</script>
<style scoped>
</style>
It looks like ".../api/v2/pokemon/" produces an object with a results array, and those results contain uri's like ".../api/v2/pokemon/(some id)"
The way to combine them is as follows:
axios.get("https://pokeapi.co/api/v2/pokemon/").then(response => {
this.basicInfo = response
let promises = this.basicInfo.map(result => {
return axios.get(result.url)
})
Promise.all(promises).then(response => {
this.advInfo = response
})
});
Now advInfo will be an array, like you expect so you can render it with v-for....
<div v-for="(pokemon2, i) in advInfo" :key="i">
<pre>{{pokemon2}}</pre>
</div>

Laravel 5: show comments using Vue.js

I am creating blog commenting system, I want to show comments for a post using vue.js.
In console, it says
Property or method "comment" is not defined on the instance but
referenced during render.
Also, when I try to catch user name, I got this error
Error in render: "TypeError: Cannot read property 'user' of undefined"
I want to show comments and users who commented to a particular post
in show.blade.php.
web.php
Route::get('results/{post}', 'ResultsController#show')->name('posts.show');
ResultsController
public function show(Post $post)
{
$recommended_posts = Post::latest()
->whereDate('date','>',date('Y-m-d'))
->where('category_id','=',$post->category_id)
->where('id','!=',$post->id)
->limit(7)
->get();
$posts['particular_post'] = $post;
$posts['recommended_posts'] = $recommended_posts;
//return $post->comments()->paginate(5); it returns objects
return view('posts.show',compact('posts'));
}
Comments.vue
<div class="reply-comment" :v-for="comment in comments">
<div class="user-comment" >
<div class="user">
<!--<img src="" alt="" >-->
<avatar :username="comment.user.name" :size="30" ></avatar>
</div>
<div class="user-name">
<span class="comment-name">{{ comment.user.name }}</span>
<p> {{ comment.body }} </p>
</div>
</div>
<div class="reply">
<div class="seemorecomments">
see more
</div>
<button class="reply-button">
<i class="fas fa-reply"></i>
</button>
</div>
</div>
<script>
import Avatar from 'vue-avatar'
export default {
props: ['post'],
components: {
Avatar
},
mounted() {
this.fetchComments()
},
data: () => ({
comments: {
data: []
}
}),
methods: {
fetchComments() {
axios.get(`/results/${this.post.id}`).then(({ data }) => {
this.comments = data
})
}
}
}
show.blade.php
<comments-component :post="{{ $posts['particular_post']->comments }}"></comments-component>
migration table
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->integer('post_id');
$table->text('body');
$table->integer('comment_id')->nullable();
$table->timestamps();
});
comment.php, I have this.
protected $with = ['user'];
You have a couple of minor issues with your Vue file that can be addressed pretty quickly.
First, you should define comments as an empty array — a collection will be returned as an array of objects to the Vue. By adding an unnecessary data property in the beginning, you are allowing the v-for loop to run in your template before the data has been retrieved.
EDIT: I'm not sure about the way you wrote this data function, so I have re-written it a way in which I'm familiar.
data() {
return {
comments: []
}
},
Second, you want to get the correct data from the response. Axios data is stored another level deep (response.data). Of course, if you are paginating the results, they are one more level deep (response.data.data).
fetchComments() {
axios.get(`/results/${this.post.id}`).then(response => {
this.comments = response.data
// or for paginated results
// this.comments = response.data.data
})
}
EDIT: Thank you for providing the Gist! I think I'm seeing things more clearly now.
Update your controller like so:
You want to load the comments into the post here.
public function show(Post $post)
{
$recommended_posts = Post::latest()
->whereDate('date','>',date('Y-m-d'))
->where('category_id','=',$post->category_id)
->where('id','!=',$post->id)
->limit(7)
->get();
// load the post comments here
$post->load('comments');
$posts['particular_post'] = $post;
$posts['recommended_posts'] = $recommended_posts;
return view('posts.show',compact('posts'));
}
And you blade like so:
Your module wants a single post, not an array of comments.
<comments-component :post="{{ $posts['particular_post'] }}"></comments-component>
And you Vue like so:
You don't actually need to use Axios at all since we've already loaded the comments.
<script>
import Avatar from 'vue-avatar'
export default {
props: ['post'],
components: {
Avatar
},
data() {
return {
comments: this.post.comments
}
},
}
</script>

Vuejs page loads before axios request returns data

Problem
Im trying to load data from my database using vuejs + axios in a Laravel project. Locally I can make the request using axios and it returns the data, assigns it to variables and then outputs the data to the DOM.
When I push the same code to production the axios request takes a little longer to execute but it seems to be long enough that vuejs is ready before all the data is returned, leaving me with an annoying 'undefined property' error when the data attempts to be displayed. Sometimes this error persist's through 20+ refreshes before the data loads.
What I think the issue is
So my guess from reading up is that axios isn't getting the data as quickly as it should do so vuejs is ready to start serving the data but axios hasn't had a chance to collect it yet, resulting in the undefined error
What i've read and tried
I've read that using v-if statements on the objects that depend on the data should fix this issue. But it doesn't, all it does it hide the object from view if the data is not present. For Example...
HTML
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
JAVASCRIPT
<script>
new Vue({
el: '#container',
data: {
user: {}
},
mounted() {
this.getUserData();
},
methods: {
getUserData(){
axios.post('/user/get').then((response) => {
// Check the response was a success
if(response.data.status === 'success')
{
this.user = response.data.user;
}
});
},
}
})
</script>
This doesn't work, it just doesn't display anything when the data hasn't been loaded yet.
Question
How do I ensure that my loaded data is displayed and not return an undefined error? The only way I can think is by allowing the user to 'click to retrieve data' on a failed attempt.
Further Information
Im not using vue templates, the child/parent structure or any vue libraries. I'm importing vue.js via CDN and using it on the page in a script as seen above. Im not sure if using it like this will cause any such limitations, i've only learnt vue.js on a basic and this works for the company and the boss...
You could add a 'loaded' boolean attribute to resolve this. For example:
<script>
new Vue({
el: '#container',
data: {
isLoaded: false,
user: {}
},
mounted() {
this.getUserData();
},
methods: {
getUserData(){
axios.post('/user/get').then((response) => {
this.isLoaded = true;
// Check the response was a success
if(response.data.status === 'success')
{
this.user = response.data.user;
}
});
},
}
})
</script>
Then wrap your html to check if the data has been loaded:
<div v-if="isLoaded">
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
</div>
You could then also display a loader:
<div v-if="isLoaded">
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
</div>
<div v-else>
Loading...
</div>
Edited answer, to explain better what I mean.
Your code should work, no matter if it takes 1 sec or 10 sec for axios to fetch the data. You shouldn't have to check with v-if on user.name. VueJS has reactivity and upates the view if the data property changes.
See this code example.
https://codepen.io/bergur/pen/VOXrzR
Here I wait 10 seconds to populate the user object
{{ user.name }} shows nothing and then Bergur
{{ computedName }} shows undefined Hallgrímsson because user.name is undefined for 10 seconds.
{{ user.address.country }} will throw an error (Cannot read property 'country' of undefined) because user.address isn't available in the initial data object.
So one solution is to define address initially in the data object
user: {
address: {}
}
My point is that your initial code should work (when rendering). The undefined errors come when
a) You're using that name property in your vue app, e.g user.name when there is no name property.
or
b) When you're rendering 2nd level property, e.g user.address.country because address hasn't been initially defined.
With the help of lodash's debounce you can delay the time between when a method function or computed property is called and excecuted.
Also you can differentiate the delay time for the same method creating different functions eg. one being triggered by mounted and the other one by a watcher. In my case the function being called on mounted, needs to wait before the axios call returns a response and sets store.state.TableDataInit. In the watcher case it can run immediately because the store.state variable already has been set, there is no need to wait for an axios response anymore. For example:
created: function() {
this.setInitRange = _.debounce(this.setRange, 600);
this.setInitBoundaries = _.debounce(this.setBoundaries, 600);
this.setWatchRange = this.setRange;
this.setWatchBoundaries = this.setBoundaries;
},
mounted() {
this.setInitRange(this.selected);
this.setInitBoundaries();
},
watch: {
selected() {
store.commit("storedMetric", this.selected);
this.setWatchRange(this.selected);
this.setWatchBoundaries();
}
},
methods: {
setRange(value) {
var metric = value;
var dataArray = store.state.TableDataInit;
const maxMetric = dataArray.reduce(function(prev, current) {
return _.get(prev, metric) > _.get(current, metric) ? prev : current;
})[metric];
const minMetric = dataArray.reduce(function(prev, current) {
return _.get(prev, metric) < _.get(current, metric) ? prev : current;
})[metric];
this.range = [minMetric, maxMetric];
store.commit("storedOutBoundRange", this.range);
},
setBoundaries() {
this.min = store.state.OutBoundRange[0];
this.max = store.state.OutBoundRange[1];
},
<div v-if="user">
<div>
#{{ user.name }}
</div>
<div>
#{{ user.second_name }}
</div>
<div>
#{{ user.phone }}
</div>
<div>
#{{ user.any }}
</div>
</div>

Alternative way of handling undefined ajax data

Something I have never truly understood in Vue is how one should tackle the issue with undefined "network/async keys".
Give the example below:
<template>
<div>
<h1>{{row.something_undefined_before_ajax_returns.name}}</h1>
</div>
</template>
<script>
export default {
data() {
return {
row: {}
}
},
created() {
axios.get('.../row/12')
.then(response => {
// response = {data: {something_undefined_before_ajax_returns: {name: 'John Doe'}}}
this.row = response.data
})
}
}
</script>
This would return in a console warning of: [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined". However, the h1 will finally display John Doe once Vue registered the async changes.
However, the way I have coped with this 'til now is to simply define the expected key in the js like so:
<script>
export default {
data() {
return {
row: {
something_undefined_before_ajax_returns: {}
}
}
},
...
}
</script>
By doing that, Vue does not throw a warning as expected. However, this does work in the short term, but once the return data from the HTTP calls gets a bit more complex and nested, this feels really pointless having to define all the possible outcomes.
Is there a way to tell Vue to ignore such warnings until the call has been received?
You can simply use v-if:
<h1 v-if="row">{{row.something_undefined_before_ajax_returns.name}}</h1>
And you may also check row property:
<h1 v-if="row && row.something_undefined_before_ajax_returns">
{{row.something_undefined_before_ajax_returns.name}}
</h1>
Sorry, I din't notice that row is an object, to check it you may use like:
v-if="Object.keys(row).length"
But I don't think you need to check for object but its property, so you can do just:
v-if="row.something_undefined_before_ajax_returns"

Categories