VueJS rendering data from REST service - asynchronous

I've attempted to render data from a http request to a component which is working fine, the issue is that it's null while the data is being fetched. While the data is null the console is throwing a TypeError until all the data is loaded and committed to the Vuex store.
All is working how I'd suspect, I'm just trying to figure how I can prevent the errors being thrown and to wait until all the appropriate data is fetched. I've seen others using v-if to check if the data is null which will work. It just seems tedious and that there surly is a better way to achieve the same outcome, without an application riddled with v-if statements checking every single state.
I came across this solution but it's still not working how I thought it would, I'm still receiving the same console errors. Am I using these key words correctly and are they in the correct location? since nothing has changed with every variation I've tried.
Vuex Action:
const actions = {
getThread ({ commit }, payload) {
Vue.http
.get(`http://localhost:9000/threads/${payload.id}`)
.then(async response => {
commit(FETCH_THREAD, await response.data)
})
}
}
This is within my vue file calling upon the action:
created () {
this.$store.dispatch('getThread', {id: '59280ab5acbafb17af9da902'})
}

I assume you are trying to display something from your store in your template. The problem is, Vue cannot render something that does not exist yet. The solution is to check whether the data exists or not.
Let's take this component example:
<template>
<div>
{{ someObject.name }}
</div>
</template>
<script>
export default {
data () {
return {
someObject: null
}
},
methods: {
fetchTheObject () {
this.someObject = {
id: 1,
name: 'My object'
}
}
},
created () {
setTimeout( () => {
this.fetchTheObject()
}, 3000)
}
}
</script>
As you can see, you will get an error in your console because someObject.name does not exist until fetchTheObject() has been called.
The solution is to put some v-if attribute to control that:
<template>
<div>
<span v-if="someObject === null">Fetching the object</span>
<span v-else>{{ someObject.name }}</span>
</div>
</template>
In general, you would want to display some spinner to show the user that something is loading...
Hope this helps
EDIT: And forget about the async await in your code, you don't need that here

Related

createPages in Gatsby issues ; duplications and unrendered content

I've had a few errors trying to render single blog posts.
I tried using the page template with /post/{post_name} and I was getting this error:
warn Non-deterministic routing danger: Attempting to create page: "/blog/", but
page "/blog" already exists
This could lead to non-deterministic routing behavior
I tried again with /blog/{post_name}.
I now have both routes, which I'm not sure how to clean up; but more importantly, on those pages, nothing renders, even though there should be an h1 with it's innerhtml set to the node.title and likewise a div for the content.
I've uploaded my config and components to https://github.com/zackrosegithub/gatsby so you can have a look.
Not sure how to fix
I just want to see my content rendered on the screen.
Developer tools don't seem to help when there's no content rendered as I can't find anything to inspect to try to access it another way.
Thank you for your help
Your approach is partially correct. You are using a promise-based approach but when using then() you are already settling and partially resolving it so you don't need to use the callback of resolve(), which may be causing a duplication of the promise function so try removing it.
Additionally, you may want to use a more friendly approach using async/await functions. Something like:
exports.createPages = async ({ graphql, actions, reporter }) => {
const yourQuery= await graphql(
`
{
allWordpressPost {
edges{
node{
id
title
slug
excerpt
content
}
}
}
}
`
if (yourQuery.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`);
return;
}
const postTemplate = path.resolve("./src/templates/post.js")
_.each(yourQuery.data.allWordpressPost.edges, edge => {
createPage({
path: `/post/${edge.node.slug}/`,
component: slash(postTemplate),
context: edge.node,
})
})
})
// and so on for the rest of the queries
};
In addition, place a console.log(pageContext) in your postTemplate to get what's reaching that point and name the template as:
const Post = ({pageContext}) => {
console.log("your pageContext is", pageContext);
return <div>
<h1>
{pageContext.title}
</h1>
</div>
}
export default Post;

Passing data to props after asynchronous call in Vue

I have set up a bare bones vue project to show the problem. The only thing I added was the axios package. The problem is when I try to set the property of child component after an asynchronous call I cant read that property in the component. If you look at the code you can see I console log several times to show when I can get the data and when I cant. Please help me figure out what im missing here.
Parent
<template>
<div id="app">
<HelloWorld :test_prop="testData" :test_prop2="testData2" :test_prop3="testData3" test_prop4="I work also"/>
<div>{{testData5}}</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import axios from 'axios';
export default {
name: 'app',
components: {
HelloWorld
},
data() {
return {
testData: '',
testData2: 'I work just fine',
testData3: '',
testData5: ''
}
},
created: function(){
var self = this;
this.testDate3 = 'I dont work';
axios.get('https://jsonplaceholder.typicode.com/posts/42').then(function(response){
//I need this one to work
self.testData = 'I dont work either';
self.testData5 = 'I work also';
});
}
}
</script>
Child
<template>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['test_prop', 'test_prop2', 'test_prop3', 'test_prop4'],
data() {
return {
comp_data: this.test_prop,
comp_data2: this.test_prop2,
comp_data3: this.test_prop3,
comp_data4: this.test_prop4
}
},
created: function(){
console.log(this.test_prop);
console.log(this.test_prop2);
console.log(this.test_prop3);
console.log(this.test_prop4);
}
}
</script>
Your console.log inside created hook will show you the initial state of this variables in Parent component. That's because Parent's created hook and Child's created hook will run at the same time.
So, when you solve your promise, Child component was already created. To understand this behavior, put your props in your template using {{ this.test_prop }}.
To solve it, depending on what you want, you can either define some default value to your props (see) or render your child component with a v-if condition. That's it, hope it helps!
On Vue created hook only the initial values of properties passed from main component. Therefore later updates (like your example "after ajax call") in main component will not effect to child component data variables because of that already child created hook take place.
If you want to update data later one way you can do like this:
watch: {
test_prop: function(newOne){
this.comp_data = newOne;
}
}
Adding watcher to property changes will update the last value of property from main component.
And also edit the typo this.testDate3. I guess it must be this.testData3

Vue JS AJAX computed property

Ok, I believe I am VERY close to having my first working Vue JS application but I keep hitting little snag after little snag. I hope this is the last little snag.
I am using vue-async-computed and axios to fetch a customer object from my API.
I am then passing that property to a child component and rendering to screen like: {{customer.fName}}.
As far as I can see, the ajax call is being made and the response coming back is expected, the problem is there is nothing on the page, the customer object doesnt seem to update after the ajax call maybe.
Here is the profile page .vue file I'm working on
http://pastebin.com/DJH9pAtU
The component has a computed property called "customer" and as I said, I can see in the network tab, that request is being made and there are no errors. The response is being sent to the child component here:
<app-customerInfo :customer="customer"></app-customerInfo>
within that component I am rendering the data to the page:
{{customer.fName}}
But, the page shows no results. Is there a way to verify the value of the property "customer" in inspector? is there something obvious I am missing?
I've been using Vue for about a year and a half, and I realize the struggle that is dealing with async data loading and that good stuff. Here's how I would set up your component:
<script>
export default {
components: {
// your components were fine
},
data: () => ({ customer: {} }),
async mounted() {
const { data } = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
this.customer = data;
}
}
</script>
so what I did was initialize customer in the data function for your component, then when the component gets mounted, send an axios call to the server. When that call returns, set this.customer to the data. And like I said in my comment above, definitely check out Vue's devtools, they make tracking down variables and events super easy!
I believed your error is with naming. The vue-async-computed plugin needs a new property of the Vue object.
computed: {
customer: async function() {
this.axios.get('/api/customer/get/' + this.$route.params.id).then(function(response){
return(response.data);
});
}
}
should be:
asyncComputed: {
async customer() {
const res = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
return res.data;
}
}

Polymer and Firebase: changing the path of <firebase-query> dynamically

I have a in a component, which fetches some data. The path is dynamic, as it has a binding inside it.
I then have some links that change the path dynamically. I would expect the list of data to update accordingly.
when I first load the page, it works all fine, but whenever I click on a link to update the path (and therefore to fetch new data), it returns nothing.
I checked what was going on with an observer, and it looks like whenever I update the path the data is updated twice: first it returns the actual data I would expect, and then it returns an empty array.
Here is the component:
<dom-module id="test-one">
<template>
<firebase-query app-name="main" path="/templates/[[template]]" data="{{items}}"></firebase-query>
<a on-tap="changeTemplate" data-template="template1">Template 1</a><br />
<a on-tap="changeTemplate" data-template="template2">Template 2</a><br />
<p>Current template is [[template]]</p>
<template is="dom-repeat" items="{{items}}" as="item">
[[item.ref]] - [[item.description]]<br />
</template>
</template>
<script>
Polymer({
is: 'test-one',
properties: {
items: {type: Array, observer: "dataChanged"},
template: {type: String, value: "template1"},
},
dataChanged: function(newData, oldData) {
console.log(newData);
},
changeTemplate: function(e) {
elm = e.currentTarget;
template = elm.getAttribute("data-template");
console.log("link has been clicked, we're changing to "+template);
this.set("template", template);
}
});
</script>
</dom-module>
This is what the console shows when I click on one of the links:
There seems to be some asynchronious sorcery going on - any idea on how to solve this?
This is effectively a bug in firebase-query, fixed with this pull request:
https://github.com/firebase/polymerfire/pull/167.
It has already been reported here: https://github.com/firebase/polymerfire/issues/100
Looks like this is a bug in Polymerfire. For now making the following changes to your local copy of firebase-database-behavior.html will fix the problem, seemingly without artifacts, however this really requires a bug report. I will get to filling a bug report as soon as I get a chance, they tend to have a lot of time consuming back and forth :(
Simply comment out line 86 in firebase-database-behavior.html. The new __pathChanged function should look as so.
__pathChanged: function(path, oldPath) {
if (oldPath != null && !this.disabled && this.__pathReady(path)) {
this.syncToMemory(function() {
// this.data = this.zeroValue;
});
}
},
What's Going On
When the path changes there is code written to zero out the old value, and this code lives in firebase-databse-behavior.html, which firebase-query inherits. This makes sense, however firebase-query already zeros out the data upon __queryChanged at line 279 in firebase-query.html.
__queryChanged: function(query, oldQuery) {
if (oldQuery) {
oldQuery.off('child_added', this.__onFirebaseChildAdded, this);
oldQuery.off('child_removed', this.__onFirebaseChildRemoved, this);
oldQuery.off('child_changed', this.__onFirebaseChildChanged, this);
oldQuery.off('child_moved', this.__onFirebaseChildMoved, this);
this.syncToMemory(function() {
this.set('data', this.zeroValue);
});
}
if (query) {
query.on('child_added', this.__onFirebaseChildAdded, this.__onError, this);
query.on('child_removed', this.__onFirebaseChildRemoved, this.__onError, this);
query.on('child_changed', this.__onFirebaseChildChanged, this.__onError, this);
query.on('child_moved', this.__onFirebaseChildMoved, this.__onError, this);
}
},
Changes to the path are first observed by firebase-query by __computeQuery at line 23 in firebase-query.html. __queryChanged is then triggered zeroing out the old data and sets up firebase event handlers to observe changes to the firebase database. Subsequently, __pathChanged in firebase-database-behavior.html is called which again zeros out the data, but after the new data has already been written by the firebase event handlers.

Meteor + React, show specific content based on user’s actions

I'm working on an app that contains send/cancel request functionality.
I have the following code:
import React, { Component, PropTypes } from 'react';
import { Events } from '../../api/collections/events.js';
import { Visitors } from '../../api/collections/visitors.js';
import { createContainer } from 'meteor/react-meteor-data';
class Event extends Component {
handleDelete() {
Event.remove(this.props.event._id);
}
requestInvite() {
let eid = Events.findOne(this.props.event._id).title;
Visitors.insert({
visitor_id: Meteor.userId(),
visitor_email: Meteor.user().emails[0].address,
event_name: eid,
})
// did it to debug function, returns correct value
console.log(Visitors.findOne({id: this._id}) + ', ' + Meteor.userId());
}
cancelInvite() {
Visitors.remove(this.props.visitor._id);
}
render() {
const visitor = this.props.visitor.visitor_id;
const length = Visitors.find({}).fetch().length;
return (
<div>
{this.props.event.owner == Meteor.userId() ?
<div>
<img src={this.props.event.picture} />
<span>{this.props.event.title}</span>
<button onClick={this.handleDelete.bind(this)}>Delete</button>
</div>
</div> :
<div>
<div>
<img src={this.props.event.picture} />
<span>{this.props.event.title}</span>
<div>
{ length > 0 && visitor == Meteor.userId() ?
<button onClick={this.cancelInvite.bind(this)}>Cancel Request</button>
:
<button onClick={this.requestInvite.bind(this)}>Request invite</button>
}
</div>
</div>
</div>
}
</div>
)
}
}
Event.propTypes = {
event: PropTypes.object.isRequired,
};
export default createContainer(() => {
return {
event: Events.findOne({id: this._id}) || {},
visitor: Visitors.findOne({id: this._id}) || {},
};
}, Event)
It works quite simple, this component shows action buttons depend on user's status (if the current user hosts this event, it shows delete related functionality and so one, I just keep is as simple as it can be for this example). If the current user isn't this event's hoster, component lets this user to send (and cancel) a request for invite. Okay, everything works as it should but only for the first user clicked on Send Request button and after that ich changes to Cancel Request (I use different browsers to test cases like this). The rest of users can also click on Send Request but for them it doesn't change to Cancel Request (but it still adds correct document to Visitors collection, also I have a component which displays all the visitors and the data is corret, i.e ids, emails and event titles). By the first time I thought it's an issue with findOne function, but I don't think so because console.log(Visitors.findOne({id: this._id}) + ', ' + Meteor.userId());'s output stays correct giving me current user's id and just created visitor's id which are the same for each case. Also I found a very strange behaviour. When the app rebuilds, send/cancel functionality works as it suppossed to for every single user.
I think I'm kinda close for the solution but need a little gotcha to do it.
Any help would be highly appreciated!
UPD
It's obvious that my question isn't full without describing Visitor document being created in this component. Here it is:
{
"_id": "Qbkhm9dsSeHyge4rT",
"visitor_id": "qunyJ4sXNfz2w8qeR",
"visitor_email": "johndoe#gmail.com",
"event_name": "test",
}
So as you can see I grab visitor_id from Meteor.userId() and that's why I'm using this.props.visitor.visitor_id to check if currently logged in user's id is equal to a particular visitor's id.
Solution
The problem was with my query to fetch visitor's ids in createContainer function. I changed it to visitor = Visitors.findOne({visitor_id: Meteor.userId()}) and it worked the way I described.
Without knowing how your Visitors collection documents are structured, it's difficult to say for sure; however, it seems that your condition for visitor == Meteor.userId() is the issue since you said that the documents are correctly being added to the Visitors collection, which would make length > 0 return true.
The issue could be that you are setting const visitor = this.props.visitor.visitor_id; rather than say const visitor = this.props.visitor._id;.

Resources