VueJS lifecycle order when combined with router - javascript

I need to run an async initialize method when my app starts. so I did it inside beforeCreate of App.vue as below.
app.vue:
export default {
...
beforeCreate () {
console.log('before create app.vue')
}
}
Also, I need to check a condition before entering any route, so I did it inside beforeEach of router.
main.js:
router.beforeEach((to, from, next) => {
if (condition) {
console.log('router before each')
}
})
The problem is I need to check the condition after my initialization is done, but, when I launch my app, the condition is checked before the initialization and my output is:
router before each
before create app.vue
What am I missing? and how can I fix this?

here is how I solved it in vue3 but you can use a similar approach... basically don't mount the app until the initialization is completed. In my case the authCheck in the initialization function you mention full source
import useFirebaseAuth from "./hooks/firebase-auth";
const { authCheck } = useFirebaseAuth();
const app = createApp(App).use(IonicVue);
authCheck()
.then(() => {
app.use(router);
return router.isReady();
})
.then(() => {
app.mount("#app");
});

Yap that is how it suppose to be, the navigation guard is always triggered whenever there is a navigation, meaning it gets executed once it receives your navigation, which is before your component gets created. You can execute anything within your main.js file i don't fully understand what you want to achieve but you can utilize any of the Vue Js lifecycle hooks(https://v2.vuejs.org/v2/guide/instance.html) to get what you want or the navigation guards(https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards) beforeRouteEnter, beforeRouteLeave and beforeRouteUpdate
They all execute at different times, hope this helps

Related

Unit test redux toolkit queries connected via fixedCacheQuery using react testing library

I have two components in my react app:
Component A
Performs a lazy fetch of users. This looks like:
const ComponentA = () => {
const [trigger, {data}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
useEffect(() => {
trigger();
}, []);
return <div>{data.map(user => user.id)}</div>
}
Component B
Wants to render a loading indicator while useLazyLoadUsers's isLoading property equals true. This component looks like this:
const ComponentB = () => {
const [, {isLoading}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
if (!isLoading) {
return <div>Users loaded</div>
}
return <div>Loading users</div>
}
The issue
While this works well (the states are in sync via the fixedLoadUsersKey), I'm struggling to find documentation or examples on how to test Component B.
Testing Component A is well documented here https://redux.js.org/usage/writing-tests#ui-and-network-testing-tools.
I already have an overwritten react testing library render method that provides a real store (which includes all my auto-generated queries).
What I would like to do is testing that Component B loading indicator renders - or not - based on a mocked isLoading value. I want to keep my current or similar implementation, not duplicating the isLoading state into another slice.
So far, I have tried mocking useLazyLoadUsers without success. I also tried dispatching an initiator before rendering the test, something like
it('should render the loading indicator', async () => {
const store = makeMockedStore();
store.dispatch(myApi.endpoints.loadUsers.initiate());
render(<ComponentB />, {store});
expect(await screen.findByText('Loading users')).toBeVisible();
})
This didn't work either.
Does someone have a hint on how to proceed here or suggestions on best practices?

Vue.js - Helper function that accesses the app instance

Let's say I want to make a utility function that redirects users to a route if they aren't authenticated.
This function resides in a utils.js file; it isn't declared in any one component.
// utils.js
export function redirectIfNotAuthenticated (app, path) {
if (!app.$store.state.isAuthenticated) {
app.$router.push(path)
}
}
this function is then imported into single-file components and called like:
redirectIfNotAuthenticated(this, '/login').
I think having an app parameter and having to explicitly pass this from the component is ugly. Is there a better way to have a stateless helper function access the scope of the calling component in vue? As in, is there a way to bind functions that don't live inside of a .vue file to the calling instance of the vue app?
To handle redirection, instead of putting logic inside component, you can use vue-router's navigation guards to handle on top level instead:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
if (!isAuthenticationRequired(to)) { // your logic to ignore authentication for custom routes, e.g. login
next();
} else if (!isAuthenticated()) { // your logic which needs access to Vuex's store
next('login');
} else {
next();
}
})
Then, if you create a store object and add it to your Vue app instance, like in the default example of Vuex , you can refer to the object directly without any need to access the Vue app, since same store object is used:
// utils.js
import store from '../your_path/your_file.js';
export function isAuthenticated() {
return store.state.isAuthenticated;
}
The best practice in this situation is to use router navigation guards :
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
next(vm=>!vm.$store.state.isAuthenticated)
//vm refers to the vue instance
})

Why am I getting `When testing, code that causes React state updates should be wrapped into act ` in one case but not another?

I'm doing some testing with jest and react-testing-library
I've boiled this down so it's obviously not my full test.
Why does this code:
test('Share renders', () => {
const { getByText } = render(<Share />)
})
Give me this console error:
When testing, code that causes React state updates should be wrapped into act
But this code:
test('Share renders', async () => {
const { getByText } = render(<Share />)
const share = await waitForElement(() => getByText(/Share/i))
expect(share).toBeVisible()
})
Does not?
My Share component makes a call to get some user info inside a useEffect hook and set it to state. This causes a rerender the first time the component is mounted. The first example then complains at me due to the state update. That makes sense. My question is, what is waitForElement doing that suddenly makes it NOT give me the error with the second example? Nothing has changed with the component being rendered and then immediately updating...but now the error goes away???

Vue Router - call function after route has loaded

I'm working on a project where I need to call a function AFTER the route has finished loading. However, when using the 'watch' functionality, it only loads on route change, but does so before route has finished loading. So when I attempt to run a script that targets DOM elements on the page, those elements don't exist yet. Is there any functionality in Vue Router that would allow me to wait until everything is rendered before running the script?
const app = new Vue({
el: '#app',
router,
watch: {
'$route': function (from, to) {
function SOMEFUNCTION()
}
},
data: {
some data
},
template: `
<router-view/>
`
})
You should use Vue.nextTick
In your case this would translate to:
const app = new Vue({
el: '#app',
router,
watch: {
$route() {
this.$nextTick(this.routeLoaded);
}
},
data() {
return {};
},
methods: {
routeLoaded() {
//Dom for the current route is loaded
}
},
mounted() {
/* The route will not be ready in the mounted hook if it's component is async
so we use $router.onReady to make sure it is.
it will fire right away if the router was already loaded, so catches all the cases.
Just understand that the watcher will also trigger in the case of an async component on mount
because the $route will change and the function will be called twice in this case,
it can easily be worked around with a local variable if necessary
*/
this.$router.onReady(() => this.routeLoaded());
},
template: `<router-view/>`
})
This will call the routeLoaded method every time the route changes (which I'm deducing is what you need since you are using the <router-view> element), if you also want to call it initially, I would recommend the mounted hook (like in the example) or the immediate flag on the watcher
In my opinion on this situation, you should use component life cycle method of the loaded component, either use mounted method or created method.
or if your script doesn't depend on any vue component (store) you can use router.afterEach hook
router.afterEach((to, from) => { if (to.name !== 'ROUTENAME'){ // do something }});
The solution for me was to set up a custom event in every page's mounted() hook with a mixin and listen for that event on the body for example. If you wanted to strictly tie it with the router's afterEach or the route watcher to ensure the route has indeed changed before the event was fired, you could probably set up a Promise in the afterEach and resolve it in the page's mounted() by either the event or sharing the resolve function through the window.
An example:
// Component.vue
watch: {
'$route': function (from, to) {
new Promise((resolve) => {
window.resolveRouteChange = resolve;
}).then(() => {
// route changed and page DOM mounted!
});
}
}
// PageComponent.vue
mounted() {
if(window.resolveRouteChange) {
window.resolveRouteChange();
window.resolveRouteChange = null;
}
}
In case of router-view, we can manually detect router-view.$el change after $route is changed
watch: {
'$route'(to, from) {
// Get $el that is our starting point
let start_el = this.$refs.routerview.$el
this.$nextTick(async function() { await this.wait_component_change(start_el)})
}
},
methods: {
on_router_view_component_changed: function() { }
wait_component_change: async function(start_el) {
// Just need to wait when $el is changed in async manner
for (let i = 0; i < 9; i++) {
console.log('calc_has_dragscroll ' + i)
if(start_el) {
if (!start_el.isSameNode(this.$refs.routerview.$el)) {
// $el changed - out goal completed
this.on_router_view_component_changed()
return
}
}
else {
// No start_el, just wait any other
if(this.$refs.routerview.$el) {
// $el changed - out goal completed too
this.on_router_view_component_changed()
return
}
}
await this.$nextTick()
}
},
}
You can accomplish this by hooking into VueJS lifecycle hooks:
Use VueJS Lifecycle Hooks:
Here is a summary of the major VueJS lifecycle hooks. Please consult the documentation for the full description.
i. beforeCreate: This function will be called before the component is created
ii. created: This function will be called after the component is created, but note although the component is created, it hasn't been mounted yet. So you won't be able to access the this of the component. However, this is a good place to make Network Requests that will update the data properties.
iii. mounted: This function is called once the component has been rendered and the elements can be accessed here. This is what you're looking for.
iv. beforeDestroy: This function is called before the component is destroyed. This can be useful to stop any listeners (setTimeout, setInterval..), that you created.
See the diagram below for the details.
const app = new Vue({
el: '#app',
router,
mounted(){
this.someFunction()
},
data: {
some data
},
template: `
<router-view/>
`
})
Use Vue Router Navigation Guards: Vue Router also expose some lifecycle hooks that can you hook into. However, as you will see below they do not fit your requirements:
i. beforeRouteEnter: called before the route that renders this component is confirmed. oes NOT have access to this component instance, because it has not been created yet when this guard is called!
ii. beforeRouteUpdate: called when the route that renders this component has changed, but this component is reused in the new route.
iii. beforeRouteLeave: called when the route that renders this component is about to be navigated away from.
References:
VueJS Documentation (LifeCycle): VueJS Instance
Vue Router Documentation (Navigation Guards): Navigation Guards

Using socketio in redux actions

I'm trying to build an application using React, Redux and socket.io !
My question is : how do you initialize event listeners (which are actions in Redux) like this one :
export const addNotif = () => (dispatch) => {
socket.on('notification', (message) => {
dispatch(notification(message))
})
}
in React without having access to the componentWillMount since I'm using functional programing on components.
My current problem is that if I pass the method
addNotif
to my component, every time a new notification comes in, the store is updated, and so the component is re-rendered and therefore it adds another socket.on event and this keeps on going.
Any ideas on how to fix that in a clean way ?
Thanks !
Try to define and export the function like this:
export const addNotif = (dispatch) => {
socket.on('notification', (message) => {
dispatch(notification(message))
})
}
As you mention, you don't want to attach an event listener inside a stateless function.
You wanna invert this: notification comes in > dispatch event. This can live outside the lifecycle of react since you can dispatch arbitrary redux actions from anywhere.
If the action is not consumed because there are no clients that's fine. You can fine tune this by absorbing/demuxing events in a redux middleware and/or have components dispatch subscriptions.
index.js
// start() should be called ONCE wherever you have access to the store
const notifications = require('./notifications');
notifications.start(store.dispatch);
notifications.js
export default {
start(dispatch) {
socker.on('connect', () => {
console.log('Subscribed to notifications');
});
socket.on('notification', (payload) => {
dispatch(({ type: "NOTIF", payload }));
});
socket.on('error', (message) => {
console.log('TODO: handle error');
})
},
};
It might be a good idea to abstract away the socketio interface, hence the two files although it's not necessary.

Categories

Resources