Call API automatically to fetch data with prop value when component is displayed in Vue.js - vue.js

I have a page which displays a list of mutual funds. With each mutual fund, I have a button to display their NAV history. This button calls a component which has an embedded API call to fetch the NAV history. I pass the fund code for which the data is to be fetched as a prop to the component. However, I am not able to trigger the API call automatically when the prop is called.
this is my code as of now:
Parent component (main page):
<template>
<!-- some code -->
<a href="#" #click="fetchNavHistory(fund)">
<v-icon small>history</v-icon>
</a>
<!-- some more code -->
<NAVHistory
:show="showNavHistory"
:amfi_code="amfi_code"
ref="history"
#hide="showNavHistory=false"
/>
</template>
export default {
name: "FundList",
components: {
NAVHistory
},
data() {
return {
showNavHistory: false,
amfi_code: 0
}
},
methods:{
fetchNavHistory(fund){
this.amfi_code = fund.amfi_code
this.showNavHistory = true
var child = this.$refs.history
child.fetchNavHistory()
}
}
}
Child component (where NAV history is displayed):
<template>
<!-- some code -->
</template>
<script>
export default {
props: {
show: Boolean,
amfi_code: Number
},
data(){
return{
apiURL: process.env.VUE_APP_BASEURL,
navHistory: [],
}
},
methods: {
async fetchNavHistory(){
try{
const response = await fetch(this.apiURL + '/navhistory', {
method: 'POST',
body: JSON.stringify({"amfi_code": this.amfi_code}),
headers: {'content-type': 'application/json; charset=UTF-8'},
})
const data = await response.json()
console.log(data)
this.navHistory = data
} catch(error){
console.log(error)
}
}
}
}
</script>
At first I tried calling the fetchNavHistory() method on updated() event. But that kept calling the API non-stop when the component was displayed on the screen.
Then I tried adding a watch for the show prop. But that didn't work at all.
Finally, as a workaround, I called the API from the parent component itself. While that is working, it is calling the component with the previous value of the amfi_code, rather than the updated value. So the first time it gets called, the amfi_code is passed as 0.
Is there a way to safely trigger the API call when the component is displayed, i.e., the show prop is set to true?

You can try watch with deep:true option that way the watch will be triggered when a component will be mounted. Or you can call API on mounted hook and check show prop in it.
deep:true means a watch will look at if changes occur not only for a watched prop but additionally at all nested props.
immediate:true means that a watch will fire after a component is mounted (when a watched prop has initial value).

Related

Is there any way to check the status of props when passing data from parent to child component

I have been troubled by a question for a long time. Now I am using Vue.js to develop a web project. What I want to do is to pass data from parent to child component. However, the child component's main program would run only after the props data was received, due to the async data transmission mechanism. So I would like to know whether these are some ways to check the status of props data in the child component. Therefore I can make sure the subsequent task would run after the data was passed.
For example, a feasible solution is axios.requset({..}).then(res => {..}).
You can use the watchers in your child component. Consider the following parent component:
Vue.component('parent-comp', {
props: ['myProp'],
template: `
<div>
<child-comp my-prop={someAsyncProp} />
</div>
`,
data() {
return {
// Declare async value
someAsyncProp: null
};
},
mounted() {
// Some async computation
axios
.requset({ url: '/get-data' })
.then(res => {
// Set value asynchronously
this.someAsyncProp = res;
});
}
});
Your child component would use watchers to check if data is available:
Vue.component('child-comp', {
props: ['myProp'],
template: '<div></div>',
watch: {
// Watch the property myProp
myProp(newValue, oldValue) {
if (newValue !== null) {
// Do something with the props that are set asynchronously by parent
}
}
}
})

how to re-mount a child component in vuejs

im searching how to re-mount a child component. Not just re-render it, but totally re-mount (so mounted() function of my child component will be called.
I know there already is a similar question (How to re-mount a component in VueJS?), but the author was searching how to re-rendre (and the solution match this case), when i follow these instructions, my mounted() function is not called.
here is what i have:
I have a page with 2 component:
an upload component
a list component
here is what i want:
I use my upload component to upload a .zip file to my api. Then, the API parse the .zip, and store its information in database.
My list il a table of every .zip, displaying basic informations (name, description...), taking data from the store, and if empty, using axios to fetch from API (everything into the mounted() hook).
the only solution i have to update the list when i upload a new .zip is to fetch the list from my API.
here is the problem: i cant find a way to update my list. Knowing data are fetched when my list component is mounted, the solution would be to re-mount my component, But I can't find how, so I'm asking you guys.
here is my page code:
<template>
<div>
<div class="col-md-4 text-center mr-auto">
<card>
<Upload #upload="upload" ></Upload>
</card>
</div>
<div>
<List :ProjID="selectedProject.id" :key="childKey"/>
</div>
</div>
</template>
<script>
import upload from "./upload.vue";
import API from "./services/API";
import list from "./list.vue";
export default {
name: "ProjectCard",
components: {
Upload,
List,
},
props: {
selectedProject: null
},
data() {
return {
childKey: 0,
};
},
methods: {
upload(file) {
API.postJob(file, this.selectedProject.id)// <-- function calling axios, everything is working here. It also delete stored file in my store, so the list component will fetch from API to get new data
.then(response => {
this.childKey += 1;
console.log("success!")
})
.catch(response => {
console.log("not succes :c")
});
}
}
};
</script>
here is my list mounted() hook:
async mounted() {
const fetch = await API.getAll(); <-- this function fetch from my store, and if nothing comes out, fetch from API.
if (fetch == 401) {
console.log("not logged in");
}
if (fetch == 500) {
console.log("error");
return();
}
this.fetchedFiles = fetch;
this.dataReady = true;
}
I have to re-call the mounted() hook in order to update my list (or any better solution)

How to dynamically mount vue component with props

Scenario / context
I have an overview component which contains a table and an add button. The add button opens a modal component. When i fill in some text fields in the modal and click the save button, a callback (given as prop) is called so the parent component (the overview) is updated. The save button also triggers the model toggle function so the model closes.
So far works everything like expected but when i want to add a second entry, the modal is "pre-filled" with the data of the recently added item.
Its clear to me that this happens because the model component keeps mounted in the background (so its just hidden). I could solve this by "reset" the modals data when the toggle function is triggered but i think there should be a better way.
I have a similar issue when i want to fetch data in a modal. Currently i call the fetch function in the mounted hook of the modal. So in this case the fetch happens when the parent component mounts the modal. This does not make sense as it should only (and each time) fetch when the modal is opened.
I think the nicest way to solve this is to mount the modal component dynamically when i click the "add" (open modal) button but i can't find how i can achieve this. This also avoids that a lot of components are mounted in the background which are possibly not used.
Screenshot
Example code
Overview:
<template>
<div>
// mount of my modal component
<example-modal
:toggleConstant = modalToggleUuid
:submitHandler = submitHandler />
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
someList: [],
}
},
mounted() {
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Modal:
<template>
<div>
<input v-model="item.type">
<input v-model="item.name">
<input v-model="item.location">
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
item: {},
}
},
mounted() {
// in some cases i fetch something here. The data should be fetched each time the modal is opened
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Question
What is the best practive to deal with the above described scenario?
Should i mount the modal component dynamically?
Do i mount the component correctly and should i reset the content all the time?
You are on the right way and in order to achieve what you want, you can approach this issue with v-if solution like this - then mounted() hook will run every time when you toggle modal and it also will not be present in DOM when you are not using it.
<template>
<div>
// mount of my modal component
<example-modal
v-if="isShowModal"
:toggleConstant="modalToggleUuid"
:submitHandler="submitHandler"
/>
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
isShowModal: false,
modalToggleUuid: someUuid,
someList: []
};
},
mounted() {},
methods: {
showModal: function() {
this.isShowModal = true;
},
submitHandler: function(item) {
this.someList.push(item);
this.isShowModal = false;
}
}
};
</script>

Which method can I use which will be called every time a prop on a component changes

I am new to Vue. I am passing props to a child component and when the props change, I am using watch to catch the changes and assign them to this.query. I am able to log this change in watch and it is working. The prop value is changing on the page but mounted is not being called again, it is only being called once when the component is rendered for the first time,. I want to be able to do a fetch each time these props change.
I have tried to play around with updated and beforeUpdate but I am unable to log the props there and so don't know where I can fetch the data every time they change.
<template>
<div class="movie-list">
<h1>{{ query }}</h1>
//At the moment I am just rendering the query which is changing as the prop changes but I want to be able to render movies incrementally from a fetch in mounted() as this.query changes
</div>
</template>
<script>
export default {
props: ['query'],
name: 'SearchedMovies',
data: function () {
return {
movies: []
}
},
watch: {
query: function(newVal){
this.query = newVal
console.log('query in watch', this.query);
//this is logging each time it changes
}
},
updated () {
console.log('query in updated', this.query);
//this is not logging
},
beforeUpdate () {
console.log('query in beforeUpdate', this.query);
//this is not logging
},
mounted () {
console.log('this is the query in SearchedMovies', this.query);
//this is logging once when the component first renders
//I want to be able to do a fetch here each time this.query changes and save the results to this.movies.
}
}
</script>
The watch will be called any time the query prop changes. You are assigning the new value — which is already in the query prop — back to the query prop, which is a bad idea, because you should not modify props.
Just put your fetch inside the watch callback.

How to defer passing of Ajax data to child components?

I'm building a simple to-do app with Vue 2.0 using built-in parent-child communication. The parent element that is directly attached to the Vue instance (new Vue({...}) is as follows:
<!-- Tasks.vue -->
<template>
<div>
<create-task-form></create-task-form>
<task-list :tasks="tasks"></task-list>
<task-list :tasks="incompleteTasks"></task-list>
<task-list :tasks="completeTasks"></task-list>
</template>
<script>
import CreateTaskForm from './CreateTaskForm.vue';
import TaskList from './TaskList.vue';
export default {
components: { CreateTaskForm, TaskList },
data() { return { tasks: [] }; },
created() { // Ajax call happens here...
axios.get('/api/v1/tasks')
.then(response => {
this.tasks = response.data;
console.log(this.tasks); // THIS IS LOGGED LAST
});
},
computed: {
completeTasks() {
return this.tasks.filter(task => task.complete);
},
incompleteTasks() {
return this.tasks.filter(task => task.complete);
}
}
}
</script>
The idea is that <tasks></tasks> will display a form to create a new task, as well 3 lists - all tasks, incomplete, and complete tasks. Each list is the same component:
<!-- TaskList.vue -->
<template>
<ul>
<li v-for="task in taskList">
<input type="checkbox" v-model="task.complete"> {{ task.name }}
</li>
</ul>
</template>
<script>
export default {
data() { return { taskList: [] }; },
props: ['tasks'],
mounted() {
this.taskList = this.tasks;
console.log(this.tasks); // THIS IS LOGGED FIRST
}
}
</script>
Problem
As you can see, I am trying to pass data from <tasks> to each of the 3 <task-lists>, using dynamic :tasks property:
<task-list :tasks="tasks"></task-list>
<task-list :tasks="incompleteTasks"></task-list>
<task-list :tasks="completeTasks"></task-list>
Note, I am not using shared (global) state, because each list needs a different portion of data, even though these portions of data belong to the same store. But the problem is that :tasks are assigned an empty array before the Ajax call happens; and as I am guessing, props are immutable, hence tasks in the child <task-list> are never updated when data is fetched in the parent <tasks>. In fact, <task-list> is created first (see the log), and only then the data is fetched using Ajax.
Questions
How do I defer the passing of data from <tasks> to my <task-list>s? How do I make sure that all components refer to the single source of truth that's updated dynamically?
Is there a way to solve this parent-child communication problem with "vanilla" Vue.js? Or do I need to use Vuex or something similar?
Am I right in using properties to pass data to children? Or should I use shared store in a global variable?
The problem is here: mounted() { this.taskList = this.tasks; ...} inside TaskList.vue as taskList property is updated only on mounted event.
There's a trivial solution in Vue: you should make taskList a computed property which depends on props, so that when parent data changes your computed property gets updated:
props: ['tasks'],
computed: {
taskList: function() {
return this.tasks;
}
}
Don't forget to remove taskList from data block.
Also, I would rewrite v-model="task.complete" into #change="$emit('something', task.id)" to inform a parent component that status has changed (and listen to ). Otherwise parent will never know the box is checked. You can then listen for this event on parent component to change tasks status accordingly. More reading: https://v2.vuejs.org/v2/guide/components.html#Custom-Events
You can also use watch property of vue instance
watch:{
tasks(newVal){
this.taskList = newVal;
//do something
}
}