I have a page with a lot of inputs where the user can change the details of an event and after that the user can click on the save button which would trigger the saveChanges() method which would re-render the page with the new inputs. This would be only a mock implementation for now as the backend is not ready so if there would be a full page reload the changes would disappear.
I store the changes in my store.state and would like to see if there is a store.state.newEvent in my preloader if there is then the params.event should be the store.state.newEvent if not then the original event object.
data() {
return {
event: this.$route.params.event,
newEvent: { ...this.$route.params.event},
...
};
},
saveChanges() {
store.setNewEvent(this.newEvent);
this.$router
.push({
name: RouteNames.EventManagement.name,
params: {
ID: this.event.id,
},
});
},
my router
const routes = [
{
name: RouteNames.EventManagement.name,
path: RouteNames.EventManagement.path,
beforeEnter: async to => {
await managementPreload.preload(to);
},
component: EventManagement,
},
]
my preloader
async preload(to) {
store.showSpinner();
console.log(store.state.newEvent);
if (store.state.newEvent) {
to.params.event= store.state.newEvent;
} else {
let data = await EventManagementManager.getById({
id: to.params.ID,
});
to.params.event= data.event;
}
store.hideSpinner();
return to;
}
When I try to call the saveChanges() the console.log does not even get triggered so I guess the program does not get to the preloader at all.
Related
Here is the page to create a User
const [createType, { loading, data }] = useMutation(CREATE_USER_CLASS) //mutation query
const createUserClass = async (event) => {
event.preventDefault();
try {
const { data } = await createType({
variables: {
userClassName,
},
refetchQueries: [{ query: STACKINFO }],
options: {
awaitRefetchQueries: true,
},
});
setNotification({
message: 'User class created successfully',
code: 200,
});
handleClose();
} catch (e) {
setNotification({ message: e.message, code: 400 });
handleClose();
}
};
The thing is I can see inside the network tab the API is calling twice, which is not a good way, but I can see the newly added data , but the page is not refreshing. Kindly help me
I was also struggling with a similar problem and I stepped into your question. I don't know which version of Apollo Client you are using, but I think that instead of using refetchQueries() method, you can try to use update() to clear the cache. This way you will notify UI of the change. Something like this:
createType({
variables: {
userClassName,
},
update(cache) {
cache.modify({
fields: {
// Field you want to udpate
},
});
},
})
This is a link for reference from official documentation's page:
https://www.apollographql.com/docs/react/data/mutations/#:~:text=12-,update
I hope it helps!
I am trying create a to fetch data from the server and show them to the user. But the issue is that nothing apart from the dummy information is displayed.
Basically there are two scenarios:
1. Navigating to the page from some other link (This works as expected)
Explanation: Such as going from http://localhost:3000/ to http://localhost:3000/assignments/explore and it renders all the fetched contents as expected.
Vue plugin ss
2. Entering the page directly through the url or press refresh
Explanation: By directly typing http://localhost:3000/assignments/explore in the url nothing is displayed apart from the dummy card
Vue plugin ss
As you can see the length of the assignment state is 1 instead of 3 and the vuex action saveAssignments is also missing in this case
Template tag in explore.vue
...
<div v-for="assignment in assignments" :key="assignment._id">
<Card :assignment="assignment"></Card>
</div>
...
Script Tag in explore.vue
fetch() {
this.$store.dispatch('assignment/fetchAssignment')
},
computed: {
assignments() {
return this.$store.state.assignment.assignments
},
},
assignment.js //Vuex Store
export const state = () => ({
assignments: [ //dummy data
{
_id: '5f1295181ebf00dd0070de1',
title: 'dummy',
Description: 'asfd',
Price: 50,
createdAt: '2020-07-18T06:22:09.037Z',
updatedAt: '2020-07-18T06:22:09.037Z',
__v: 0,
id: '5f12951081ebf00dd0070de1',
},
],
})
export const mutations = {
saveAssignments(state, newAssignments) {
state.assignments = state.assignments.concat(newAssignments)
},
}
export const actions = {
async fetchAssignment({ commit }) {
const data = await this.$axios.$get('assignments')
commit('saveAssignments', data)
},
}
Any help will be appreciated
Found the solution
I just had to add the return statement before the dispatch function in fetch()
fetch(){
return this.$store.dispatch('assignment/fetchAssignment')
}
Found my answer here
Nuxtjs async await in a page doesnt work on page refresh
Not specific to Vue.js but to Javascript Single Page applications. If you have a form and a rather long running submit action, like saving something. The submit operation should save something and then pushing to a new route for a success message.
While waiting for the result, the user clicks on a different link and is going away.
See this fiddle:
https://jsfiddle.net/hajbgt28/4/
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
methods: {
async submit() {
await setTimeout(() => {
this.$router.push("/bar");
}, 5000);
}
}
};
const Foo = { template: '<div>Foo</div>' }
const Bar = { template: '<div>Bar</div>' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
})
new Vue({
router,
el: '#app',
data: {
msg: 'Hello World'
}
})
Click Home
Click the button
Click on "Foo" immediately, you see "Foo"
Wait a few seconds
The Page changes to "Bar"
I have a two solutions in my mind:
I can check inside the submit operation if I am still on the route I expect and only proceed if the user is still on this page. It is rather complicated to check it every time
Disable all links on the page while loading. But this makes the page useless until the operation is finished.
What is the best practice for situations like this?
You could use a beforeRouteLeave navigation guard to abort that action (i.e., cancel the timer in your example) upon switching routes.
Assuming identifiable submit actions, save the ID of the operation result (i.e., save the timer ID from setTimeout's return value in your example).
Add a beforeRouteLeave handler to the component to cancel the submit action (i.e., clear the timer ID in your example).
const Home = {
methods: {
submit() {
this.timerId /* 1 */ = setTimeout(() => {
this.$router.push("/bar");
}, 5000);
}
},
beforeRouteLeave (to, from, next) {
clearTimeout(this.timerId) /* 2 */
next()
}
};
updated jsfiddle
Here's one idea: make a component that provides (using Vue's provide/inject API):
A function that starts an operation. This is called when a form is sent. It provides a whenDone callback which is either executed or ignored, depending on if the operation is cancelled.
A function that cancels all pending operations. The cancel function could be called when the user navigates away.
The implementation could look like this:
const CancellableOperationProvider = {
name: "CancellableOperationProvider",
props: {},
data: () => ({
pendingOperations: []
}),
/*
* Here we provide the theme and colorMode we received
* from the props
*/
provide() {
return {
$addOperation(func) {
this.pendingOperations.push(func);
func(function whenDone(callback) {
if (this.pendingOperations.includes(func)) callback();
});
},
$cancelAllOperations() {
this.pendingOperations = [];
}
};
},
render() {
return this.$slots.default[0];
}
};
The usage would look like this:
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
inject: ['$addOperation', '$cancelAllOperations'],
methods: {
async submit() {
this.$addOperation(whenDone => {
await setTimeout(() => {
whenDone(() => this.$router.push("/bar"));
}, 5000);
});
}
}
};
You could then add a navigation guard to the Vue Router so that $cancelAllOperations is called after clicking any link. Since $cancelAllOperations is only accessible through the inject API you will have to make a component that imperatively adds a navigation guard to the Vue router after mounting and removes it when unmounting.
Let me know if it doesn't work--I haven't done Vue in a while.
I used the answer from tony19 to make solution which fits my needs for use cases without setTimeout too:
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
data() {
return {
onThisPage: true
}
},
beforeRouteLeave(to, from, next) {
this.onThisPage = false;
next();
},
methods: {
submit() {
setTimeout(() => {
if (this.onThisPage) {
this.$router.push("/bar");
}
}, 5000);
}
}
};
See here: https://jsfiddle.net/ovmse1jg/
I have an Iron Router route where in waitOn I subscribe to a publication.
If I load my main page first and then click the link that references that route everything is fine. But if I reload the page the subscription doesn't work: it returns an empty collection.
Router.route('/profileEdit/:_id',
{
waitOn: function () {
return Meteor.subscribe('getUser');
},
data: function () {
let user = Meteor.users.findOne({_id:this.params._id});
if (!user) {
throw new Error('user undefined');
}
let profile = {
'profile.firstName': user.profile.firstName,
'profile.lastName': user.profile.lastName,
email: user.emails[0].address,
'profile.age':user.profile.age,
'profile.gender':user.profile.gender,
'profile.aboutMe':user.profile.aboutMe,
}
return profile;
},
name: 'profileEdit'
}
);
I have a login issue with website that uses:
Vue.js v2.0.3
vue-router v2.0.1
vuex v0.8.2
In routes.js I have a simple interceptor setup
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
And in login.vue,which handles the login page logic after using Google API only for login succeeds I call this:
this.login(userData).then(
() => this.$router.push(this.redirectToAfterLogin), // Login success
() => {} // Login failed
)
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
this.$router.push(this.redirectToAfterLogins)
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})}}
computed: {
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
return this.$route.query.redirect
}else{
return '/'
}
}
}
router.js
var VueRouter = require('vue-router')
// Router setup
export const router = new VueRouter({
linkActiveClass: "is-active",
mode: 'history',
saveScrollPosition: true,
routes: [
{ path: '', name: 'root', redirect: '/home' },
{ path: '/login', name: 'login', meta: { loadingNotRequired: true }, component: require('./pages/login.vue') },
{ path: '/logout', name: 'logout', meta: { loadingNotRequired: true }, component: require('./pages/logout.vue') },
{ path: '/home', name: 'home', title: 'Home', redirect: '/home/random', component: require('./pages/home.vue'),
children: [
{ path: 'random', name: 'random', meta: { requiresAuth: true }, title: 'Random', component: require('./pages/random.vue') }
]
}
]
})
// Redirect to login page if not logged In
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
Now here this.login is just the call to vuex, to update the logged in user.
What happens is that after login, URL changes to /home, but the DOM does not update!
Only way that successfully changed the DOM was forcing location.reload() and that is not what I want to do, as it loses my dynamically loaded G scripts in Head.
Any idea on what to do to force the view to update DOM?
NOTE: it happens only on the first login of user, if he logs out and back-in, the redirecting is fine
Not a perfect solution may be, as it is going to recreate the component but it will work for every case when having same route & needs to update the component.
Just update the <router-view/> or <router-view></router-view> with
<router-view :key="$route.fullPath"></router-view>
Vue re-uses components where possible. You should use beforeRouteUpdate to react to a route switch that uses the same component.
I have the same problem "URL changes to /home, but the DOM does not update".
In my project, the tag "transition" maked the problem.
Hope it is helpful!
Maybe you should set the redirectToAfterLogin function into methods, like this it will be recalculated each times. The computed will be modified only if used v-model changed. To stick to the meaning of the function name, I would set the router push inside.
login.vue :
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
// this.$router.push(this.redirectToAfterLogins)
this.redirectToAfterLogins()
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})
}
},
// computed: {
methods: {
this.login(userData).then(
// () => this.$router.push(this.redirectToAfterLogin), // Login success
() => this.redirectToAfterLogin(), // Login success
() => {} // Login failed
),
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
// return this.$route.query.redirect
this.$router.push(this.$route.query.redirect)
}else{
// return '/'
this.$router.push('/')
}
}
}
https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties
https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
"However, the difference is that computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again."
methods vs computed and filters :
Access vue instance/data inside filter method