Vue.js emit event from child component not catched - javascript

I have a child component which fetch some data from my server, before fetching I change the loading status to true and I want to set it to false after the fetch is completed. So I do something like that in my child component:
mounted() {
this.$emit('update:loadingMessage', 'Loading version options from Artifactory...');
this.$emit('update:isLoading', true);
this.fetchVersions();
},
methods: {
fetchVersions() {
const promises = [
this.$http.get(`${process.env.API_URL}/version/front`),
this.$http.get(`${process.env.API_URL}/version/back`),
];
Promise.all(promises)
.then((values) => {
// Do some stuff
})
.then(() => {
this.$emit('update:isLoading', false);
})
.catch(requestService.handleError.bind(this));
},
},
And in my parent component I listen to this event like that:
<version-selector
:form="form"
#update:loadingMessage="updateLoadingMessage"
#update:isLoading="updateLoadingStatus"
:isSnapshotVersion="isSnapshotVersion">
</version-selector>
Finally in the updateLoadingStatus I set the isLoading value to true or false accordingly.
updateLoadingMessage(message) {
this.$log.debug(message);
this.loadingMessage = message;
},
updateLoadingStatus(status) {
this.$log.debug(status);
this.isLoading = status;
},
This is useful to display or not my loading component:
<loading
v-if="isLoading"
:loadingMessage="loadingMessage"
:isGiphy="true">
</loading>
My problem is that the first emit is working and the isLoading value is set to true but the second one is not working and my isLoading value stay to true forever... In my method updateLoadingStatus I log the status value and I see that this method is just called once.

I solved the problem by using v-show instead of v-if in my template.
<loading
v-show="isLoading"
:loadingMessage="loadingMessage"
:isGiphy="true">
</loading>

I know this is an old question, but I stumbled into a similar situation today, and finally understood why the mechanism was working with v-show, and not v-if.
If you get the following <template> tag:
<div>
<component-a v-if="isLoading" />
<component-b v-else #set-loading-status="setIsLoading" />
</div>
And the following <script> tag:
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
export default {
name: 'ParentComponent',
components: { ComponentA, ComponentB },
data() {
return {
isLoading: false,
}
},
methods: {
setIsLoading(isLoading) {
this.isLoading = isLoading
},
},
}
It seems fine, right? You can catch the set-loading-status event from the <component-b> and set it to true. But you can't catch it again to set it back to false.
But, let's take a look in the official Vue docs about v-if and v-show:
v-if is โ€œrealโ€ conditional rendering because it ensures that event listeners and child components inside the conditional block are properly destroyed and re-created during toggles.
Now you can see that the component-b gets destroyed when isLoading is set to true, and you won't be able to catch the emitted event to change it back to false.
So, in this particular case, you must use v-show to handle the loading status.

Related

Do something after fetch and mounted complete

I have a simple nuxt.js component like the one below.
Chart is a component that has a method which will receive the data that is fetched in fetch().
If I simply call that method after await fetch('...') I get an error when it's rendered on client-side since the Chart component has not yet been mounted. How could I go about to do something after fetch AND mounted?
And I can't do it in mounted() because then I can't be sure that the fetch is complete.
<template>
<div>
<!--Custom component-->
<Chart ref="chart"/>
</div>
</template>
<script>
export default {
data(){
return {
chartData: []
}
},
async fetch() {
this.chartData = await fetch('https://api.mocki.io/v1/b1e7c87c').then(res =>
res.json()
)
this.$refs.chart.insertSeries(this.chartData) // doesn't work because Chart is not mounted yet.
},
}
</script>
The preferred way of handling this situation would be to use a prop so that <Chart> can handle the data itself, and watch the prop in the child.
Parent
<Chart :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
watch: {
chartData(newValue) {
if(newValue.length) {
this.insertSeries(newValue);
}
}
},
...
}
Variation: You could use v-if instead of a watch:
Parent
<Chart v-if="chartData.length" :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
created() {
this.insertSeries(this.chartData); // `chartData` is guaranteed to exist
}
...
}
Note: There is a slight difference that can emerge between these two options. Imagine you wanted a loading animation while chart data was loading.
With the first option, since the component is shown immediately, the loading functionality would have to be put in the child. In the second option, it would be put in the parent (in a v-else).

VueJS 3: updated() hook not called eventhough I update data inside mounted() hook

So I have been trying to learn Vue, and I have a parent component which has an array thats empty initially, and an isLoading which is false. I use axios to fetch data inside the mounted hook, and update the array with the response data.
To manage loading, I set isLoading to true before the axios call, and set it to false in the resolution of that axios call.
I tried consoling my isLoading data in mounted() and updated() hooks, and updated hook doesnt seem to be called at all.
The actual problem I am trying to solve is, I am passing these two data items inside provide() for another child component to use. The child component correctly shows the data that is fetched using axios in the parent, but isLoading prop is not updated inside the child component when accessed using inject.
I have work arounds like I can check for the length of the array, but I really want to understand what is happening here and why.
Providing the valid code snippets below, and any help would be greatly appreciated.
Parent data, provide(), mounted and updated hooks:
data() {
return {
friends: [],
isLoading: false,
};
},
provide() {
return {
friends: this.friends,
toggleFavourite: this.toggleFavourite,
addFriend: this.addFriend,
isLoading: this.isLoading,
};
},
mounted() {
this.isLoading = true;
console.log("mounted -> this.isLoading", this.isLoading);
axios.get(*url*).then((res) => {
Object.keys(res.data).forEach((key) => {
this.friends.push({ ...res.data[key] });
});
this.isLoading = false;
console.log("mounted -> this.isLoading", this.isLoading);
});
},
updated() {
console.log("updated -> this.isLoading", this.isLoading);
},
Child inject:
inject: ["friends", "toggleFavourite", "isLoading"]
Child usage of isLoading:
<span v-if="isLoading">Loading...</span>
<div class="friends-container" v-else>
<friend
v-for="friend in friends"
:key="friend.id"
:name="friend.name"
:email="friend.email"
:hobbies="friend.hobbies"
:age="friend.age"
:rating="friend.rating"
:favourite="friend.favourite"
#toggle-fav="toggleFavourite"
></friend>
</div>
According to the official docs :
...in most cases you should avoid changing state inside the hook. To react to state changes, it's usually better to use a computed property or watcher instead.
So you could use a watcher property like :
data() {
return {
friends: [],
isLoading: false,
};
},
provide() {
return {
friends: this.friends,
toggleFavourite: this.toggleFavourite,
addFriend: this.addFriend,
isLoading: this.isLoading,
};
},
mounted() {
this.isLoading = true;
console.log("mounted -> this.isLoading", this.isLoading);
axios.get(*url*).then((res) => {
Object.keys(res.data).forEach((key) => {
this.friends.push({ ...res.data[key] });
});
this.isLoading = false;
console.log("mounted -> this.isLoading", this.isLoading);
});
},
watch:{
isLoading(newVal,oldVal){
console.log("watch -> this.isLoading",newVal)
}
}
The trouble of this is in the provide & inject. Those are not a reactive data. So when isLoading is changed in the ParentComponent it is not changed in the ChildComponent. The ChildComponent still has an initial value of isLoading.
There are some tricks to solve that, but this is another question.
Hope that helps ๐Ÿ˜‰

How to pass state values from a component into another child component that contains param settings?

I have a basic component that goes out and gets user info via axios and then sets the users state. But in the component, I have another nested component that is a form type component that sets placeholders, defaultValue, etc.
This is the lifecyle method that gets the data and sets state:
componentDidMount() {
axios.get('https://niftyURLforGettingData')
.then(response => {
console.log(response.data);
const users = response.data;
this.setState({ users });
})
.catch(error => {
console.log(error);
});
}
Nested within this component is my form component:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: "I NEED VALUE HERE: this.state.users.id",
}
/>
if I use just:
{this.state.users.id} outside the component it works... but inside form...nothing.
I am quite sure I have to pass the state into this component... but can't quite get it.
I am pretty sure it doesn't work because users is undefined when your component renders for the first time.
Try to initialize that variable in the state doing something like this:
state = {
users: {}
}
and then use a fallback since id will also be undefined doing this:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: this.state.users.id || "Fallback Value", // this will render "Fallback value" if users.id is undefined
}
/>
If this is not the case please share more information about your situation.

How to set watcher for router-view i vue

I'm begginer in vue and i can't resolve my problem with VueRouter.
I got main app component like
<template>
<div>
<Header />
<router-view />
<Footer />
</div>
</template>
One of my Router components has an function to get data from database.
import axios from 'axios';
export default {
name: 'ComponentName',
data() {
return {
dataFromDatabase: []
}
},
methods: {
getData: function() {
// Axios get function to get data from database and add it to this.dataFromDatabase array
}
},
created() {
this.getData();
}
}
Given data are based on url params and it should be changeable when clicking on link that are in header or in other places in a whole app. The problem is that it cannot change if the component won't reload. I know that the problem is that function is called after component is created and is not called again.
So my question is:
Is there any way to watch for url params changes (adding this.$route.params.param to watch() function is not working). Maybe there is a better way to set up my routes or other way to call a function except of created() function or maybe component would reload everytime the link change. As i said links to this can be everywhere even in components that are not setted up in Router
You probably just need watch which by the way is not a function but an object with methods inside
watch: {
'$route'() {
// do something
}
}
you can use a smart watcher that will be watching since the component was created:
watch: {
'$route': {
immediate: true,
handler(newValue, oldValue) {
// ...
}
}
}

React Pre-Render Conditionals

Before I load my React App I need to check 2 conditions
User is Logged in, if not redirect to login page
All of the
User Settings fetched using API, if not display a loading screen.
So, inside render method, I have below conditions:
if (!this.isUserLoggedIn()) return <NotifyPleaseLogin />;
else if (!this.state.PageCheck) {
return (
<PageLoading
clientId={Config.clientId}
setPageReady={this.setPageReady()}
/>
);
} else {
return "Display the page";
In this scenario, what I expect to see happen is that, if user is not logged in, user redirected to login page. If user logged in and currently page is fetching the API query, user will see the PageLoading component (loading screen) and lastly if page is ready, the page will get displayed.
Right now I am getting Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state. error, which is because I am doing a setState update within Render method of the parent and also I am getting TypeError: props.setPageReady is not a function at PageLoading.js:29 error when I try to run parent's function that sets the state of PageReady to true like below
setPageReady() {
this.setState({ PageCheck: true });
}
How can I set this up so child can display a loading page until the page is ready (During this child can do an API call and retrieve user settings) then let parent know all settings are retrieved and are in the redux so parent can proceed loading the page?
You can easily achieve this by adding more states to actively control your component:
state = {
isAuthorized: false,
pagecheck: false
};
We move the authorization check to a lifecylcle-method so it doesn't get called every render.
componentDidMount() {
if(this.isUserLoggedIn()) {
this.setState({
isAuthorized: true
});
}
}
Using our state, we decide what to render.
render() {
const {
pagecheck,
isAuthorized
} = this.state;
if(!isAuthorized){
return <NotifyPleaseLogin />;
}
if(!pagecheck) {
return (
<PageLoading
clientId={Config.clientId}
setPageReady={() => this.setPageReady()}
/>
);
}
return "Display the page";
}
Note: Previously you passed this.setPageReady() to Pageloading. This however executes the function and passes the result to Pageloading. If you want to pass the function you either need to remove the braces this.setPageReady or wrap it into another function () => this.setPageReady()
You can pass PageCheck as prop from Parent to and show/hide loader in component based on that prop.
<PageLoading
clientId={Config.clientId}
pageCheck={this.state.PageCheck}
setPageReady={this.setPageReady}
/>
Then call setPageReady inside the success and error of the API call that you make in the child function:
axios.get(api)
.then((response) => {
//assign or do required stuff for success
this.props.setPageReady();
})
.catch((error) => {
//do error related stuff
this.props.setPageReady(); //as you need to hide loader for error condition as well
})
state = {
isAuthorized: false,
pageCheck: false
};
componentDidMount() {
if(this.isUserLoggedIn()) {
this.setState({
isAuthorized: true
});
}
}
{!this.state.isAuthorized ?
<NotifyPleaseLogin />
:
(!this.state.pageCheck ?
<PageLoading
clientId={Config.clientId}
setPageReady={this.setPageReady()}
/>
:
"Display the page")
}

Categories