(Beginner here)
I'm trying to create an app in Vue.js that would let me interact with GitHub API. I tried to use vue-github-api at first, but because of troubles working with pagination I switched to octokat.js.
I tried to use the same template that was suggested in vue-github-api docs and just switch to the octokat.
For brevity I'll include this small sample:
<template>
<div class="row">
<div class="col-sm-3">
<div class="panel panel-default">
{{ user.name }}
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
userData: {},
issuesList: {}
}
},
mounted: function () {
var Octokat = require('octokat')
var octo = new Octokat({
token: 'xxxxx'
})
octo.user.fetch(function (e, val) {
this.userData = Object.assign({}, val)
// or this.userData = val (not sure, which is correct)
})
},
computed: {
user: function () {
if (this.userData) {
return this.userData
}
return 'Uh-oh...'
}
}
}
</script>
<style></style>
What I'm getting is:
Uncaught TypeError: Cannot set property 'userData' of undefined
If I would do this:
this.userData = octo.user.fetch()
Then if i console.log it I get this:
[]
I just can't seem to be able to get the desired output out of the "fetch" command.
Has anybody dealt with something similar, or did you spot a glaring error?
Let's look at the code fetching from the GitHub API:
octo.user.fetch(function (e, val) {
this.userData = Object.assign({}, val)
// or this.userData = val (not sure, which is correct)
});
Inside your callback function you are attempting to assign to this.userData however it is not working.
What you have to look at here is what is this referring to?
You are assigning this.userData inside the callback to octo.user.fetch. As a result this is not bound to your Vue instance – it would be bound to the function calling it or in your case undefined because the transpiler most likely added a 'use-strict' pragma.
What can I do about this?
In ES6 we have arrow functions. Arrow functions do not have this bound to it, therefore this is still referring to your Vue instance.
octo.user.fetch((e, val) => {
this.userData = Object.assign({}, val)
});
Further reading
You Don't Know JS: this & Object Prototypes, Chapter 1: this Or That?
You Don't Know JS: this & Object Prototypes, Chapter 2: this All Makes Sense Now!
this documentation on MDN
Related
I'm trying to accomplish the following but I don't even know if it is even possible with Vue as I'm struggling to get the desired result:
I have an endpoint for an API which returns many objects within an array.
I am successfully rendering the data within my Vue application but I wanted to know if it is possible for Vue to "track" when the array has been updated with more objects and then render those in the view.
I am using setInterval to perform a GET request every 10 minutes and the new data is going into the object within my data() correctly but the changes are not reflected within the view.
At the moment I am changing a boolean from true to false at the beginning and end respectively so that the view is rendered again with v-if.
My goal is to create a simple Twitter feed app that performs a GET request every 10 minutes, collects the tweets, puts them into my Vue instance and show them in the view without having to reload the page/re-render the component. Like an automatic Twitter feed that just constantly loads new tweets every 10 minutes.
Is this even possible? I've tried using the Vue.set() method but that hasn't made any difference.
If it's not possible, what would be the best way to implement something similar?
Here is my code:
JavaScript:
new Vue({
el: '#app',
data: {
items: [],
},
created() {
this.load();
setInterval(() => this.load(), 5000);
},
methods: {
load() {
axios.get('https://reqres.in/api/users?page=2')
.then(response => {
this.items = response.data.data;
});
}
}
});
HTML
<div id="app">
<p v-for="item in items">
{{ item.first_name }}
</p>
</div>
CodePen: https://codepen.io/tomhartley97/pen/VwZpZNG
In the above code, if the array is updated by the GET request, the chances are not reflected within the view?
Yes it is possible. The way you need to set new reactive properties in your Vue instance is the following:
For Object properties: Vue.set(this.baseObject, key, value)
The baseObject cannot be a Vue instance or the base data() object, so you will have to declare a container property.
For Array entries use native array methods: e.g. Array.prototype.push().
Using Vue.set(array, arrayIndex, newArrayElement) does not work
Hence, your solution might look something line that:
<script>
export default {
data() {
return {
response: [],
};
},
mounted() {
setInterval = (() => this.getData), 600000);
}
methods: {
async getData() {
const res = await request();
const resLength = res.data.length;
for (let i = 0; i < resLength; i++) {
// check if entry is already in array
const entryExists = this.response.some((entry) => {
return entry.id === res.data[i].id
})
if (!entryExists) {
// this will make the array entries responsive, but not nested Objects
this.response.push(res.data[i]);
// to create nested responsive Objects you will have to set them explicitly
// e.g. Vue.set(this.response[this.response.indexOf(res.data[i])], nestedObjectKey, res.data[i].nestedObject)
}
}
}
}
};
</script>
Well, I view the codepen, I known why your view do not get update: the api response always return the same array!
Try to return different data.
The api returns an array, so the data defines
data() {
return {
array: [] // array that api returns
}
}
The template may look like this
<div v-for="item in array">
</div>
And the update methods
update() {
setInterval(async () => {
let resp = await api()
this.array = resp.data.concat(this.array)
}, TEN_MINUTES)
}
I feel like I am running out of ideas on how to solve this issue.
So I have a component that should read a file and display some data from that file. I want to pass only the filename to component so that it can handle reading and parsing the file. To do this I added a property to the component.
The issue I seem to have is that I can't really access that property from the data function, and if I add a watcher on the property I can parse the file as expected, but I can't seem to get that data into the DOM.
This is what I have right now:
<template>
<main :key="fileName">
fileName: {{fileName}}
<div class="post">{{data}}</div>
<div class="info">
<div v-for="item in info" v-bind:key="item.name">{{item.name}}</div>
</div>
</main>
</template>
<script>
const { parse } = require("#/service/parser");
const fs = require("fs");
let postInfo = { data: "abc", info: [] };
export default {
props: ["fileName"],
watch: {
fileName: {
immediate: true,
handler: (newVal, oldVal) => {
if (newVal) {
postInfo = parse(
fs
.readFileSync(__dirname + "/../../assets/" + newVal, "utf8")
.split("\n")
);
}
}
}
},
data: () => {
return postInfo;
}
};
</script>
I am obviously completely new to Vue, and I'm probably missing something stupid here.
So what am I doing wrong and how do I get the parsed data into my DOM?
Don't use an arrow function for your data function. Arrow functions bind this to whatever context the function is declared in. You need to let Vue properly bind this to the instance it is creating. So use
data() {
return postInfo;
}
or if for some reason you need to be old school:
data: function () {
return postInfo;
}
In one of my components I have the following functions:
addNewIndicator(attrs = {}) {
const value = attrs.value || 'Indicator'
const type = attrs.type || 'Generic Type'
this.createIndicator(value).then(
console.log('Indicator Created.')
)
}
async createIndicator(value) {
await this.props.createIndicatorMutation({
variables: {
value
},
update: (store, { data: { indicator }} ) => {
const data = store.readQuery({ query: INDICATOR_FEED_QUERY })
data.indicatorFeed.splice(0, 0, indicator)
store.writeQuery({
query: INDICATOR_FEED_QUERY,
data,
})
}
})
}
addNewIndicator() is triggered on a button click. When it runs, I get the following error:
TypeError: this.createIndicator is not a function
It is pointing to this line:
this.createIndicator(value).then(
I've done quite a bit of Googling, but haven't been able to figure out why this is the case. My understanding is that async functions can be called like that, but perhaps I'm missing something. Sorry if this is a silly question, I'm still learning React!
Also, I created the project using create-react-app and haven't modified it much other than adding some packages. Thanks for any help!
Edit to add how it is called. It is called from a child component props:
<Button primary onClick={this.handleAddSelectionClick}>Add Selected As Indicator</Button>
and handleAddSelectionClick:
handleAddSelectionClick = () => {
...snip...
this.props.addNewIndicator({
value: new_indicator_str,
})
}
try to validate that your 2 methods using the same context (this) -
you might need to do something like that in the constructor:
this.addNewIndicator = this.addNewIndicator.bind(this);
this.createIndicator = this.createIndicator.bind(this);
You probably just forgot to bind “this”. As you passed addNewIndicator as a callback, it lost its context. In this article several methods of binding callbacks are described, with all pros and cons of each.
https://reactjs.org/docs/handling-events.html
This works, but I need to use mounted(){} to initiate the function which I think can be avoided but not sure how.
<script>
export default {
data () {
return {
domains: [],
}
},
methods: {
fetchDomains() {
let _this = this;
api._get({url: 'api/domains'})
.then(function (response) {
_this.domains = response.data;
})
}
},
mounted() {
this.fetchDomains()
}
}
</script>
This code doesn't work, but I like to do something like this. Initiating the function in data(){} itself.
<script>
export default {
data () {
return {
domains: this.fetchDomains(),
}
},
methods: {
fetchDomains() {
let data = [];
api._get({url: 'api/domains'})
.then(function (response) {
data = response.data;
})
return data
}
}
}
</script>
Thanks in advance.
Your first code snippet is the correct way to do it.
You can't initialize domains with the data from the API response because it is an async operation which may or may not be resolved successfully at some point in the future, well after the component is mounted. You might also want to do other things like keeping track of the async operation with a loading property which you set to true for the duration of the request.
Your component will initially be in a loading state which does not yet have any domains data, and you need to account for this. Show a loading spinner or something during this time.
I agree with Decade Moon that your first approach is the better way to do it (though you could use created instead of mounted).
The reason your second approach doesn't work is that you return an array and then replace the local variable's value with a different array. What you need to do is populate the array you returned.
new Vue({
el: '#app',
data() {
return {item: this.getItem()}
},
methods: {
getItem() {
let val = [];
setTimeout(() => {
const result = ['first','second','third'];
val.push(...result);
}, 800);
return val;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">{{item}}</div>
I might be deviating slightly from the question (since it explicitly mentions the data property), but I think this might be helpful. Personally, if I want to provide some data with more complex logic I use the computed property. This is great in my opinion and you can read more about it in the docs. The problem in this case is that it doesn't work entirely as expected with asynchronous operations...
However, there is a lovely little module called vue-async-computed which can be found here. It solves this specific problem by providing an asyncComputed property and keeps the code really clean!
Hi guys I am using Vue JS to try and loop through my data. Here is my whole JS file:
var contentful = require('contentful');
var client = contentful.createClient({
space: 'HIDDEN',
accessToken: 'HIDDEN'
});
Vue.component('careers', {
template: '<div><div v-for="career in careerData">{{ fields.jobDescription }}</div></div>',
data: function() {
return {
careerData: []
}
},
created: function() {
this.fetchData();
},
methods: {
fetchData: function() {
client.getEntries()
.then(function (entries) {
// log the title for all the entries that have it
entries.items.forEach(function (entry) {
if(entry.fields.jobTitle) {
this.careerData = entries.items;
}
})
});
}
}
});
var app = new Vue({
el: '#app'
});
I am using methods to access some data from Contentful, once it has grabbed the necessary data it is sent to my data object.
If I console.log(careerData); within my console the following data is returned:
So I'd expect if I used v-for within my template and tried iterating over careerData it would render correctly however on my front-end I am left with an empty div like so:
<div id="app"><div></div></div>
I am currently pulling my component into my HTML like so:
<div id="app">
<careers></careers>
</div>
No errors are displayed within my console, can you think of any reason this might be happening?
Thanks, Nick
Several problems I think. As #dfsq said, you should use a arrow function if you want to keep context (this).
fetchData: function() {
client.getEntries()
.then(entries => {
this.careerData = entries.items
});
}
Then you may replace {{fields.jobDescription}} by {{career.fields.jobDescription}}, as #unholysheep wrote.
It may work. If it does not, you could add a this.$forceUpdate(); right after this.fetchData();
Use arrow function in forEach callback so you don't loose context:
fetchData: function() {
client.getEntries()
.then(entries => {
this.careerData = entries.items
});
}