Child component doesn't get updated when props changes - javascript

I am having trouble understanding how vue js's reactivity work. I am trying to make a component for all kinds of ajax call, the problem is when I change the props to be passed on to the component it doesn't get the changes in the data.
https://jsfiddle.net/8t4vx44r/34/
Vue.component('api-call-template', {
template: '#api-call-templates',
props: {
apiCallParams: Object
},
data () {
return {
result: [],
loading: false
}
},
created() {
console.log("entered?")
console.log(this.apiCallParams);
var vm = this;
vm.loading = true;
axios({
method: this.apiCallParams.method,
url: this.apiCallParams.url,
data: this.apiCallParams.params
}).then(function(response) {
vm.result = response.data;
vm.loading = false;
vm.$emit('get-response', response);
});
console.log(vm.loading)
}
});
var vm = new Vue({
el: '#app',
data: {
apiCallParams: {
url: '',
method: '',
params: {}
},
author: 'aa'
},
created() {
this.apiCallParams.url = 'https://reqres.in/api/users/1';
this.apiCallParams.method = 'get';
console.log("passing this");
console.log(this.apiCallParams);
},
methods: {
search() {
console.log("searching")
this.url = 'https://reqres.in/api/users/2';
this.apiCallParams.method = 'get';
},
getResponse(response) {
console.log("back to parent");
console.log(response);
}
}
});
<script src="https://unpkg.com/axios#0.18.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
try {{title}}
<post :title="title" :author="author" :content="content">
</post>
</div>
<template id="post-template">
<h1>{{ title }}</h1>
<h4>{{ author }}</h4>
<p>{{ content }}</p>
</template>
If you click the "next user button" it updates the data in the parent component but it doesn't call my api-call-template.

You are only calling one time because your logic is at created(), which is called only once (when the component is created).
Considering your component is:
<api-call-template :api-call-params="apiCallParams" #get-response="getResponse">...
And you want to request every time api-call-params changes. What you could do is:
Move your logic from created() to a method, say performRequest()
Call this.performRequest() on created()
Add a watcher to apiCallParams prop
Call this.performRequest() on apiCallParams watcher
See demo below.
Vue.component('api-call-template', {
template: '#api-call-templates',
props: {
apiCallParams: Object
},
data() {
return {
result: [],
loading: false
}
},
methods: {
performRequest() {
console.log("entered?")
console.log(this.apiCallParams);
var vm = this;
vm.loading = true;
axios({
method: this.apiCallParams.method,
url: this.apiCallParams.url,
data: this.apiCallParams.params
}).then(function(response) {
vm.result = response.data;
vm.loading = false;
vm.$emit('get-response', response);
});
console.log(vm.loading)
}
},
created() {
this.performRequest();
},
watch: {
apiCallParams() {
this.performRequest();
}
}
});
var vm = new Vue({
el: '#app',
data: {
apiCallParams: {
url: '',
method: '',
params: {}
}
},
created() {
this.apiCallParams = {url: 'https://reqres.in/api/users/1', method: 'get'}
console.log("passing this");
console.log(this.apiCallParams);
},
methods: {
search() {
console.log("searching")
this.apiCallParams = {url: 'https://reqres.in/api/users/2', method: 'get'};
},
getResponse(response) {
console.log("back to parent");
console.log(response);
}
}
});
<script src="https://unpkg.com/axios#0.18.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<button #click="search">next user</button>
<api-call-template :api-call-params="apiCallParams" #get-response="getResponse"></api-call-template>
</div>
<template id="api-call-templates">
<div>
<main>
<div v-if="loading">loading</div>
<div class="wrapper">
<div class="row">
<div v-for="res in result" :key="res.id">
<div class="col-md-4 cards">
<div>
results
<h3>{{ res.first_name }}</h3>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
There is only one caveat: to trigger the watch, change apiCallParams at once, like:
this.apiCallParams = {url: 'https://reqres.in/api/users/2', method: 'get'};
Not property by property, like:
this.apiCallParams.url = 'https://reqres.in/api/users/2'; // don't do like this
this.apiCallParams.method = 'get'; // don't do like this
As Vue won't pick up changes if you do it prop by prop.

Related

Why won't the data from my api render in my Vue template?

I've been scratching my head about this for a while. I'm new to Vue and can't seem to understand why this isn't working.
My template...
<template>
<div>
<div v-if="loading" class="loading">Loading...</div>
<div v-if="dbhs">
<h1>adfoij</h1>
<p class="mb-0" v-if="dbhs.length === 0">
You have not any DBHs.
</p>
<div v-else>
<div v-for="dbh in dbhs">{{dbh.dbh}} - {{dbh.count}}</div>
</div>
</div>
</div>
</template>
My script...
<script>
export default {
data() {
return {
loading: true,
dbhs: null
};
},
created() {
this.getDbhs();
},
methods: {
ajaxAxiosGetFunc: async function (url) {
var output = '';
await axios({
method: 'post',
url: url,
data: {},
responseType: 'json',
})
.then(function (response) {
//output = JSON.parse(response.data);
output = response.data;
}.bind(this))
.catch(function (error) {
console.log('ajax error');
});
return output
},
getDbhs: async function(){
var estimate_id = document.getElementById('estimate_id').value
var output = await this.ajaxAxiosGetFunc('/estimate/'+estimate_id+'/getTreesSummary/dbh'); // called asynchronously to wait till we get response back
this.dbhs = output;
console.log(output);
this.loading = false;
},
}
}
</script>
I'm getting data back from the API... it prints out in the console fine but the length of dbhs is always 0.
Anyone have any ideas?
It's because your method uses function keyword which overrides this that refers to the vue instance, change your anonymous function to an arrow function should work:
getDbhs: async () => {
var estimate_id = document.getElementById('estimate_id').value
var output = await this.ajaxAxiosGetFunc('/estimate/'+estimate_id+'/getTreesSummary/dbh'); // called asynchronously to wait till we get response back
this.dbhs = output;
console.log(output);
this.loading = false;
},

MathJax not always rendering with Vue filtered list

I'm trying to build a filtered list in Vue with equations rendered with MathJax, and it seems to be having some problems, since the equations renders on the first load, but when I search for terms, some equations render and some don't, I can't understand why.
Basically on the first load, if I type a character in my search bar, everything renders correctly, but when I search more, or erase it and do it again, it doesn't, as you can see in these images:
my Vue code is as follows:
var analisiMatematica = new Vue({
el: '#searcher',
data: {
searchQuery: '',
isResult: false,
results: [],
//json: 'json/analisimatematica.json',
error: ''
},
mounted: function() {
axios.get('./json/analisimatematica.json')
.then(function(response) {
this.results = response.data.Domande;
console.log(this.results);
}.bind(this))
.catch(function(error) {
this.error = error.data;
console.log(error.data);
}.bind(this));
},
methods: {
removeSearchQuery: function() {
this.searchQuery = '';
this.isResult = false;
},
submitSearch: function() {
this.isResult = true;
}
},
computed: {
filteredObj: function() {
var list = [];
this.results.forEach(function(el) {
if(el.domanda.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1) {
list.push(el);
}
}.bind(this))
return list;
}
}
});
MathJax is loaded in my html file's <head> like this:
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
}
});
</script>
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-MML-AM_CHTML">
</script>
While the vue app section is like this:
<div id="searcher">
<p v-show="error" v-html="error"></p>
<form class="searchForm" v-on:submit.prevent="submitSearch">
<input type="text" name="queryInput" v-model="searchQuery" placeholder="Che domanda cerchi?" #keyup="submitSearch">
<span v-show="searchQuery" class="removeInput" #click="removeSearchQuery">+</span>
</form>
<div class="results" v-show="isResult">
<ul>
<li v-for="result in filteredObj">
<p id="domanda">{{ result.domanda }}</p>
<p id="risposta">{{ result.risposta }}</p>
</li>
</ul>
</div>
</div>
All you need is to trigger MathJax to render again when filteredObj is changed. Watch filteredObj:
watch: {
filteredObj: function () {
if ('MathJax' in window) {
this.$nextTick(function() {
MathJax.Hub.Queue(["Typeset",MathJax.Hub, document.body])
});
}
}
}
var analisiMatematica = new Vue({
el: '#searcher',
data: {
searchQuery: '',
isResult: false,
results: [],
//json: 'json/analisimatematica.json',
error: ''
},
mounted: function() {
this.results = [{domanda: '$a+b=c$', risposta: '$a+b=c$'}]
},
methods: {
removeSearchQuery: function() {
this.searchQuery = '';
this.isResult = false;
},
submitSearch: function() {
this.isResult = true;
}
},
computed: {
filteredObj: function() {
var list = [];
this.results.forEach(function(el) {
if(el.domanda.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1) {
list.push(el);
}
}.bind(this))
return list;
}
},
watch: {
filteredObj: function () {
if ('MathJax' in window) {
this.$nextTick(function() {
MathJax.Hub.Queue(["Typeset",MathJax.Hub, document.body])
});
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
}
});
</script>
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-MML-AM_CHTML">
</script>
<div id="searcher">
<p v-show="error" v-html="error"></p>
<form class="searchForm" v-on:submit.prevent="submitSearch">
<input type="text" name="queryInput" v-model="searchQuery" placeholder="Che domanda cerchi?" #keyup="submitSearch">
<span v-show="searchQuery" class="removeInput" #click="removeSearchQuery">+</span>
</form>
<div class="results" v-show="isResult">
<ul>
<li v-for="result in filteredObj">
<p id="domanda">{{ result.domanda }}</p>
<p id="risposta">{{ result.risposta }}</p>
</li>
</ul>
</div>
</div>

can not change data in the #change vuejs handler

There is a component that contains input[type=file].
Also, this field has an uploadFile handler, which calls the validateMessage method, which attempts to change the error. As you can see, after changing this.error it shows that everything is correct. But in div.error it is not displayed and if you look in vueDevtool, then there is also empty.
data in vueDevTools
data() {
return {
error: ''
}
},
methods: {
validateFile(file) {
if (! file.type.includes('video/')) {
this.error = 'wrong format';
console.log(this.error); // wrong format
}
},
uploadFile(e) {
const file = e.target.files[0];
this.validateFile(file);
},
}
<input type="file"
id="im_video"
name="im_video"
#change="uploadFile"
class="hidden">
<div class="error">
{{ error }}
</div>
Here is working example.
new Vue({
el:'#app',
data() {
return {
error: ''
}
},
methods: {
validateFile(file) {
console.log(file.type);
if (! file.type.includes('video/')) {
this.error = 'wrong format';
//console.log(this.error); // wrong format
}
},
uploadFile(e) {
this.error = '';
const file = e.target.files[0];
this.validateFile(file);
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<input type="file"
id="im_video"
name="im_video"
#change="uploadFile"
class="hidden">
<div class="error">
{{ error }}
</div>
</div>
If you are using component this would help more to share data from child to parent in your case setting error from child component to parent
Vue.component('upload-file',{
template:`<div><input type="file"
id="im_video"
name="im_video"
#change="uploadFile"
class="hidden"></div>`,
data() {
return {
error: ''
}
},
methods: {
validateFile(file) {
//
if (! file.type.includes('video/')) {
vm.$emit('filerror', 'wrong format');
}
},
uploadFile(e) {
vm.$emit('filerror', '');
const file = e.target.files[0];
this.validateFile(file);
},
}
});
const vm = new Vue({
el:'#app',
mounted(){
this.$on('filerror', function (msg) {
this.error = msg;
})
},
data:{
error:''
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<upload-file></upload-file>
<div class="error">
{{ error }}
</div>
</div>

Render the binding value in dynamic generated html template in vuejs2

For following code: I want the "test1 span" could be changed with javascript. How can I do it? NOTE: the {{msg}} maybe from ajax output.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
</head>
<body>
<!-- app -->
<div id="app">
<span v-html="test"></span>
<span v-html="test1"></span>
{{test3}}
</div>
<script>
var app1 = new Vue({
el: '#app',
data: {
test: '<p style="color: red">THIS IS HTML</p>',
test1: '{{msg}}',
test3: 20,
msg: 10
}
})
function change() {
app1.msg = Math.random()
app1.test3 = Math.random()
}
setInterval(change, 2000)
</script>
</body>
</html>
Modification:
Maybe I need to make my question clear:
For next modify code, when launch the page, you will see Go to Foo link in the page, then you click the link, you will see hello {{msg}}
NOTE: this comes from the remote server: b.html.
I set a timer there, every 2 seconds, change the value of msg, I wish the {{msg}} in page could change to a random number.
main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.min.js"></script>
</head>
<body>
<div id="app">
<p>
<router-link to="/foo">Go to Foo</router-link>
</p>
<router-view></router-view>
</div>
<script>
const Foo = {
template: '<div v-html="template1"></div>',
data: function () {
return {
template1: null
}
},
created: function () {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData () {
var that = this
$.get("http://localhost/try/b.html", function(data, status) {
that.template1 = data
})
}
}
}
const routes = [
{ path: '/foo', component: Foo }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
function change() {
app.msg = Math.random()
}
setInterval(change, 2000)
</script>
</body>
</html>
b.html
<div>
hello
{{msg}}
</div>
Use the tools that Vue.js gives you. Put change() in your VM's methods object, then create a created() hook that sets up the interval.
Please note that v-html expects a String, not a Number, so just add .toString() when generating the random number.
var app1 = new Vue({
el: '#app',
data: {
test: '<p style="color: red">THIS IS HTML</p>',
test1: null,
test3: 20,
msg: 10
},
watch: {
msg: function(newVal, oldVal) {
this.test1 = newVal
}
},
methods: {
change() {
this.msg = Math.random().toString()
this.test3 = Math.random()
}
},
created() {
setInterval(this.change, 2000)
}
})
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<div id="app">
<span v-html="test"></span>
<span v-html="test1"></span>
{{ test3 }}
</div>
Instead of a watcher it is even easier to go for a computed property instead.
var app1 = new Vue({
el: '#app',
data: {
test: '<p style="color: red">THIS IS HTML</p>',
test3: 20,
msg: 10
},
computed: {
test1() {
return this.msg.toString()
}
},
methods: {
change() {
this.msg = Math.random()
this.test3 = Math.random()
}
},
created() {
setInterval(this.change, 2000)
}
})
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<div id="app">
<span v-html="test"></span>
<span v-html="test1"></span>
{{ test3 }}
</div>
Use v-model instead of v-html :-
var app1 = new Vue({
el: '#app',
data: {
test: '<p style="color: red">THIS IS HTML</p>',
test1: '{{msg}}',
test3: 20,
msg: 10
}
})
function change() {
app1.msg = Math.random()
app1.test3 = Math.random()
}
setInterval(change, 2000)
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<div id="app">
<span v-html="test"></span>
<span v-model="test1">{{msg}}</span>
{{test3}}
</div>
Finally I get the answer from here
Vue.compile( template )
this will make the template from remote server be parsed again by vuejs.
main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.min.js"></script>
</head>
<body>
<div id="app">
<p>
<router-link to="/foo">Go to Foo</router-link>
</p>
<router-view></router-view>
</div>
<script>
const Foo = {
data: function () {
return {
template1: null,
msg: null
}
},
created: function () {
this.fetchData()
setInterval(this.change, 2000)
},
render: function(createElement) {
if (this.template1)
{
return this.template1();
}
},
methods: {
change() {
this.msg = Math.random()
},
fetchData () {
var that = this
$.get("http://localhost/try/b.html", function(data, status) {
that.template1 = Vue.compile(data).render
})
}
}
}
const routes = [
{ path: '/foo', component: Foo }
]
const router = new VueRouter({
routes
})
const app1 = new Vue({
router
}).$mount('#app')
</script>
</body>
</html>
b.html
<div>
hello
{{msg}}
</div>

Vue loop list - append the ajax returned data into the list?

How can I append vue list after ajax call?
This is my HTML with Vue:
Load More
<div id="news-posts">
<!-- item -->
<template v-for="item in items">
<div class="cell large-4 medium-6">
<!-- image has no padding -->
<div class="card grid-item">
<div class="card-divider">
<h4 class="heading heading-card"><a :href=item.url><span v-html=item.title></span></a></h4>
</div>
<div class="card-date">
<span v-html=item.date></span>
</div>
<div class="card-section"><p v-html=item.excerpt></p></div>
<div class="text-right">
<a :href=item.url class="button button-more"><i class="material-icons">chevron_right</i></a>
</div>
</div>
</div>
<!-- item -->
</template>
<!-- vue - loop -->
</div>
Js:
// Render template with Vue.
// Get json of catering menu.
var element = document.getElementById('news-posts')
var buttonLoad = document.getElementById('button-load')
if (element !== null) {
// var endpoint = $('#button-load').data('posts-endpoint') // jQuery
var endpoint = buttonLoad.getAttribute('data-posts-endpoint') // Vanilla JS
var getData = await axios.get(endpoint)
var cateringMenu = new Vue({
el: '#news-posts',
data: {
items: getData.data
}
})
}
$("#button-load").click(function(){
var endpoint = buttonLoad.getAttribute('data-posts-endpoint') // Vanilla JS
$.get(endpoint, function(data, status){
var cateringMenu = new Vue({
el: '#news-posts',
data: {
items: data
}
})
})
return false
})
Of course, it does not append the ajax returned data into the list.
Any idea?
EDIT:
Got it working with:
methods: {
fetch: function (event) {
var self = this
// `this` inside methods points to the Vue instance
$.ajax({
url: endpoint,
method: 'GET',
success: function (data) {
data.map(function(item) {
self.items.push(item)
})
},
error: function (error) {
console.log(error)
}
})
}
}
Have you tried to make your button execute a method that is defined in Vue, so you have access to the instance and can set the items with this, something like this;
methods: {
fetch: function(){
var endpoint = buttonLoad.getAttribute('data-posts-endpoint');
$.get(endpoint, function(data, status){
this.items = data; //set items.
})
}
}
and then add the fecth method to your button (needs to be in the vue wrapper, its outside at the moment it looks like) v-on:click.prevent="this.fecth"

Categories

Resources