Destroyed component called 'updated' hook - vue.js

Version
Vue#2.5.16
Vuex#3.0.1
VueRouter#3.0.1
Code
First my code looked like this
export default {
//...
updated () {
//TODO
}
destroyed () {
this.unregisterModule(module.name)
}
}
But when the app get other route, this component will call the updated once and cause some problems.
Now I use the _isDestroyed state property to resolve this:
updated () {
if (!this._isDestroyed) {
//TODO
}
}
Question
How to understand this logic of hooks?

Related

How to use async/await in vue lifecycle hooks with vuex?

When I dispatch an action in App.vue component in mounted() lifecycle hook, it runs after other components load. I am using async/await in my action and mounted lifecycle hook.
App.vue file
methods: {
...mapActions({
setUsers: "setUsers",
}),
},
async mounted() {
try {
await this.setUsers();
} catch (error) {
if (error) {
console.log(error);
}
}
},
action.js file:
async setUsers(context) {
try {
const response = await axios.get('/get-users');
console.log('setting users');
if (response.data.success) {
context.commit('setUsers', {
data: response.data.data,
});
}
} catch (error) {
if (error) {
throw error;
}
}
},
In Users list component, I need to get users from vuex. So I am using mapGetters to get Users list.
...mapGetters({
getUsers: "getUsers",
}),
mounted() {
console.log(this.getUsers);
},
But the problem is "setting users" console log in running after console logging the this.getUsers.
In Users list component, I can use getUsers in the template but when I try to console log this.getUsers it gives nothing.
How can I run app.vue file before running any other components?
You are using async await correctly in your components. It's important to understand that async await does not hold off the execution of your component, and your component will still render and go through the different lifecycle hooks such as mounted.
What async await does is hold off the execution of the current context, if you're using it inside a function, the code after the await will happen after the promise resolves, and in your case you're using it in the created lifecycle hook, which means that the code inside the mounted lifecycle hook which is a function, will get resolved after the await.
So what you want to do, is to make sure you render a component only when data is received.
Here's how to do it:
If the component is a child component of the parent, you can use v-if, then when the data comes set data to true, like this:
data() {
return {
hasData: false,
}
}
async mounted() {
const users = await fetchUsers()
this.hasData = true;
}
<SomeComponent v-if="hasData" />
If the component is not a child of the parent, you can use a watcher to let you know when the component has rendered. When using watch you can to be careful because it will happen every time a change happens.
A simple rule of thumb is to use watch with variables that don't change often, if the data you're getting is mostly read only you can use the data, if not you can add a property to Vuex such as loadingUsers.
Here's an example of how to do this:
data: {
return {
hasData: false,
}
},
computed: {
isLoading() {
return this.$store.state.app.users;
}
},
watch: {
isLoading(isLoading) {
if (!isLoading) {
this.hasData = true;
}
}
}
<SomeComponent v-if="hasData" />
if you're fetching a data from an API, then it is better to dispatch the action inside of created where the DOM is not yet rendered but you can still use "this" instead of mounted. Here is an example if you're working with Vuex modules:
created() {
this.fetchUsers();
},
methods: {
async fetchUsers() {
await this.$store.dispatch('user/setUsers');
},
},
computed: {
usersGetters() {
// getters here
},
},
Question: Do you expect to run await this.setUsers(); every time when the app is loaded (no matter which page/component is being shown)?
If so, then your App.vue is fine. And in your 'Users list component' it's also fine to use mapGetters to get the values (note it should be in computed). The problem is that you should 'wait' for the setUsers action to complete first, so that your getUsers in the component can have value.
A easy way to fix this is using Conditional Rendering and only renders component when getUsers is defined. Possibly you can add a v-if to your parent component of 'Users list component' and only loads it when v-if="getUsers" is true. Then your mounted logic would also work fine (as the data is already there).

Nuxt Js Event Fires Twice

I am using Nuxt js SSR for an app that am build, I installed Vue Event plugin but when i emit an event it runs twice at the listener. Created hook runs twice too.
Modules am using:
Axios, Auth, Toast
Child Component
methods: {
initPlaylist(songs) {
console.log(songs)
}
},
mounted () {
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})
}
Parent Component
method () {
playAll (songs) {
this.$events.$emit('playAll', songs)
}
}
How can i resolve this issues guys? I need your help.
Maybe you have to call that parent's method on client side only.
you can write code like this to prevent emit on server side:
methods: {
playAll(songs) {
if (process.server) return
this.$events.$emit('playAll', songs)
}
}
or do not call playAll method on server side. (eg: created, mounted...)
You need to off that event first before.
this.$events.$off("playAll");
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})

How to observe/subscribe to changes in the state in Aurelia Store?

I have a property selectedOption on the state of my Aurelia Store, which can be changed via actions. I want to observe/subscribe to any changes to this property on the state. My problem is the subscription within the BindingEngine doesn't work because every time you change the state, you create a new copy of the state, therefore the subscription no longer works.
Here is my example:
import { Disposable, BindingEngine, autoinject } from "aurelia-framework";
import { connectTo, dispatchify } from "aurelia-store";
#autoinject()
#connectTo()
export class Holiday {
subscription: Disposable;
state: any;
constructor(private bindingEngine: BindingEngine) {
}
async updateState()
{
await dispatchify(changeSelectedOption)();
}
attached() {
this.subscription = this.bindingEngine
.propertyObserver(this.state, 'selectedOption')
.subscribe((newValue, oldValue) => {
console.log("something has changed!")
});
}
}
export class State {
selectedOption: number = 0;
}
export const changeSelectedOption = (state: State) => {
let updatedState = { ...state };
updatedState.selectedOption++;
return updatedState;
}
store.registerAction("changeSelectedOption", changeSelectedOption);
The first time, my subscription will work and the console will log "something has changed!" as the state is the same object, but it won't work after.
Another solution I could use would be to have a computed property like so:
#computedFrom("state.selectedOption")
get selectedOptionChanged()
{
return console.log("something has changed!");
}
This is a hack, and this computed won't ever be triggered as it is not bound to anything in the HTML.
For context, I want to trigger a server call every time the selectedOption property changes.
What can I do to receive all updates from the property on the state?
The thing here is that the state observable exposed by the Store is a RxJS stream. So with the advent of the new "multi-selector" feature for connectTo you could create two bindings. By implementing a hook called selectorKey Changed, in your sample selectedOptionChanged it would get called on every change of said property.
#connectTo({
selector: {
state: (store) => store.state, // the complete state if you need
selectedOption: (store) => store.state.pluck("selectedOption")
}
})
class MyVM {
...
selectedOptionChanged(newState, oldState) {
// notification about new state
}
}
Instead of store.state.pluck("selectedOption") you can also experiment with additional conditions when to notify about changes like adding distinctUntilChanged etc.
Read more about multi-selectors in the updated docs.
Alternatively if you don't want to use the connectTo decorator, simply use the state property and create another subscription

How to get state updated after dispatch

I'm new with react-native and redux and I want to know how can I get the state updated after the dispatch...
Follow my code:
/LoginForm.js
function mapStateToProps(state) { return { user: state.userReducer }; }
function mapDispatchToProps(dispatch) {
return {
login: (username, password) => {
dispatch(login(username, password)); // update state loggedIn
}
}
}
const LoginForm = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginForm;
/Login.js
---Here I've a button which calls this method loginOnPress()
loginOnPress() {
const { username, password } = this.state;
this.props.login(username, password);
console.log(this.props.user.loggedIn)
}
According my code above, I call first the method 'this.props.login(username, password);' that calls the dispatch and change the state 'loggedIn'.
And after that I try to get the state updated but without success:
console.log(this.props.user.loggedIn)
Note: When I click the second time on this button the state comes updated
Calling dispatch will update the state immediately but your components will be updated a little bit later so you can use componentWillReceiveProps to react to changes in the props, you can have a look here for a better explanation of how state change works in React
The function this.props.login(username, password) dispatches a login action on the redux-state.
Launching store.getState() will indeed immediately get you the redux-state after the update, but usually, you don't really need to do it because of the redux connect function that wraps your component.
The redux connect function updates your component with new props, so what you would usually do is "catch" these changes in one of the following functions of the react lifecycle:
class Greeting extends React.Component {
...
loginOnPress () {
const { username, password } = this.state;
this.props.login(username, password);
}
// before the new props are applied
componentWillReceiveProps (nextProps) {
console.log(nextProps.user.loggedIn)
}
// just before the update
componentWillUpdate (nextProps, nextState) {
console.log(nextProps.user.loggedIn)
}
// immediately after the update
componentDidUpdate (prevProps, prevState) {
console.log(this.props.user.loggedIn)
}
render() {
...
}
}

Aurelia event won't fire on first page load

I'm using Aurelia's EventAggregator to publish and subscribe to events in my app. Some of my custom elements take a while to load, so I've used a loading event to tell my main app.js to add a spinner to the page during loading.
This works fine once the app has loaded and I start switching between routes, however, on first page load the event doesn't seem to fire - or at least, it isn't picked up by the subscribe method.
Here's basically what my app.js does:
attached () {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// If main is loading
if (isLoading) {
document.documentElement.classList.add('main-loading');
}
// Stopped loading
else {
document.documentElement.classList.remove('main-loading');
}
});
}
And here's what my custom elements do:
constructor () {
this.eventAggregator.publish('main:loading', true);
}
attached () {
this.doSomeAsyncAction.then(() => {
this.eventAggregator.publish('main:loading', false);
});
}
This causes the first page load to not show a spinner and instead the page looks kind of broken.
Btw, I am aware of the fact that you can return a Promise from the element's attached method but I can't do this because of this other problem
Set up your subscriptions in your viewModel's constructor or activate callback
In the above example, you set up subscriptions in the viewModel's attached() callback. Unfortunately, this will not be called until all child custom element's attached() callbacks are called, which is long after any one custom element's constructor() function is called.
Try this:
app.js
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.mainLoadingSubscription = eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
}
If the viewModel is a route that can be navigated to, then handle this in the activate() callback with appropriate teardown in the deactivate() callback.
#inject(EventAggregator)
export class AppViewModel {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
activate() {
this.mainLoadingSubscription = this.eventAggregator.subscribe('main:loading', isLoading => {
// do your thing
}
}
deactivate() {
this.mainLoadingSubscription.dispose();
}
}