I am using React-Native, React-Navigation and Redux. I have a sign up page which will send the user information to the server and will listen for a token. Once the token is received, i will change the global state {userLoggedIn: true}.
In my component, i use a componentDidUpdate to check whether userLoggedIn is true, and if it is, then i will redirect to the "Main" page.
// component/register
class Register extends Component {
...
componentDidUpdate(){
console.log('componentDidUpdate from the register page')
if (this.props.people.userLoggedIn==true){
this.props.navigation.navigate('Main');
}
}
render(){
//sign up form
}
}
...
export default connect(mapStateToProps, mapDispatchToProps)(Register);
This works fine as I am redirected to the Main page. However, on the Main page itself, it is refreshing non-stop. I checked the log and saw that componentDidUpdate is still being called from the register page.
Therefore I am a little confuse why is componentDidUpdate still being called? Wouldn't it stop checking after it is being navigated to a new page?
EDIT 1: I realised it is not a problem with componentDidUpdate. I inserted the redirect condition insider the render method, it also causes an infinite loop. I can't understand why it is still trying to render when it is navigated to a new page.
It looks like your SignUp page keeps getting new props and updating after you are navigated to the Main page. I think this is due to navigation prop that is updated. In your componentDidUpdate you should make sure you are going to change page only in one case - when your userLoggedIn is changed from false to true.
// component/register
class Register extends Component {
...
componentDidUpdate(prevProps){
console.log('componentDidUpdate from the register page')
if (this.props.people.userLoggedIn === true && prevProps.people.userLoggedIn === false) {
this.props.navigation.navigate('Main');
}
}
render(){
//sign up form
}
}
You can use withNavigationFocus to know if the component is focused.
Related
I have a Vue app which does a little localStorage and server check on app load, to determine where to initially route the user.
This is in the App's main entry component, in the created() hook
My problem is that the default / route's Component visibly loads first, then the server call and everything happens which causes the user the route to their correct location
How can I delay the rendering of the initial component until my app's main component created() method completes, and then purposely navigates the user to the correct route?
I had this problem before and I firmly believe that you must have the initial files for your routes and your router configuration.
In the configuration, you could handle the permission and router before each route and with next() . In the router file, you can set your params and check them in the index.js file(router configuration)
you could also use your localStorage data in Router.beforeeach
EDIT: I just saw you used the created method... like mentioned below use beforeRouteEnter instead with the next() parameter it provides
First of all I wouldn't recommend using a delay but instead a variable that keeps track if the API call is done or not. You can achieve this using the mounted method:
data() {
return {
loaded: false,
}
}
async mounted() {
await yourAPICALL()
if (checkIfTokenIsOkay) {
return this.loaded = true;
}
// do something here when token is false
}
Now in your html only show it when loaded it true:
<div v-if="loaded">
// html
</div>
An better approuch is using the beforeRouteEnter method which allows you to not even load the page instead of not showing it: https://router.vuejs.org/guide/advanced/navigation-guards.html
I have a homepage with <router-link> tags to views. It is a simple master/detail relationship where the Homepage is a catalogue of products and the Product detail page/view shows information on each item.
When I first launch the website and click on an item on the Homepage view (e.g. URL: http://localhost:8080/100-sql-server-2019-licence), the Product view gets loaded and the product detail loads fine.
If I then press the back button in the browser to return to the Homepage and then click on a different Product (e.g. URL: http://localhost:8080/101-oracle-12c-licence), the URL in the browser address bar changes but I get the previous product's information. Its lightning quick and no new network calls are done which means its just showing a cached page of the previous product. If I then hit the refresh button while on that page, the network call is made and the correct product information is displayed.
I did a search online but couldn't find this problem described on the search results. Could anyone point me in the right direction of how to cause a refresh/re-render of a route when the route changes?
What is happening
vue-router will cache your components by default.
So when you navigate to the second product (that probably renders the same component as the first product), the component will not be instantiated again for performance reasons.
From the vue-router documentation:
For example, for a route with dynamic params /foo/:id, when we
navigate between /foo/1 and /foo/2, the same Foo component instance
will be reused.
The easy (but dirty) fix
The easy -but hacky and not recommended - way to solve this is to give your <router-view /> a key property, e.g.:
<router-view :key="$route.fullPath" />
This will force vue-router to re-instantiate the view component every time the url changes.
However you will loose all performance benefits you would normally get from the caching.
Clean fix: properly handling route changes
The clean way to solve this problem is to react to the route-change in your component (mostly this boils down to moving ajax calls from mounted into a $route watcher), e.g.:
<script>
export default {
data() {
return {
productDetails: null,
loading: false
};
},
watch: {
'$route': {
// with immediate handler gets called on first mount aswell
immediate: true,
// handler will be called every time the route changes.
// reset your local component state and fetch the new data you need here.
async handler(route) {
this.loading = true;
this.productDetails = null;
try {
// example for fetching your product data
const res = await fetch("http://give.me.product.data/" + encodeURIComponent(route.params.id));
this.productDetails = await res.json();
} finally {
this.loading = false;
}
}
}
}
};
</script>
Alternative: Navigation Guards
Alternatively you could also use vue-routers In-Component Navigation Guards to react to route changes:
<script>
export default {
async beforeRouteUpdate (to, from, next) {
// TODO: The route has changed.
// The old route is in `from`, the new route in `to`.
this.productData = await getProductDataFromSomewhere();
// route will not change before you haven't called `next()`
next();
}
};
</script>
The downside of the navigation guards is that you can only use them directly in the component that the route renders.
So you can't use navigation guards in components deeper within the hierarchy.
The upside is that the browser will not view your site before you call next(), which gives you time to load the data necessary before your route is displayed.
Some helpful ressources
Vue Router Navigation Guards Documentation
vue-router github issue
Similar Question about vue-router component reuse on stackoverflow
I have a some middleware in my Nuxt app that closes a fullscreen mobile menu when a new route is clicked. What I am experiencing is the following:
user clicks nuxt-link
menu closes
asyncData delay (still shows current page)
new page is loaded upon resolved asyncData
What I would like is the following:
user clicks nuxt-link
asyncData delay (if exists)
menu closes upon new page load
Is it possible to have an asyncData watcher in Nuxt middleware?
I know I can hack this by creating a store value that tracks asyncData load, but I'd rather not have to wire up such a messy solution to each and every page that uses asyncData.
Note - not every page uses asyncData.
I think that the best option would be to leave the menu open, and then when the new page finishes loading (probably on the mounted hook) send an event or action to close the menu.
I figured this out in a more elegant solution than having to implement a store.dispatch function on each individual asyncData function.
So what I did was use a custom loading component: https://nuxtjs.org/api/configuration-loading/#using-a-custom-loading-component
However, instead of showing a progress bar or any sort of loading animation, I just used this component to set a loading state in my vuex store. Note - it forces you to have a template, but I just permanently disabled it.
<template>
<div v-if="false"></div>
</template>
<script>
export default {
methods: {
start() {
this.$store.dispatch('updateLoadingStatus', true);
},
finish() {
this.$store.dispatch('updateLoadingStatus', false);
}
}
};
</script>
Then in my middleware, I set up an interval watcher to check when loading has stopped. When stopped, I stop the interval and close the mobile menu.
export default function({ store }) {
if (store.state.page.mobileNav) {
if (store.state.page.loading) {
var watcher = setInterval(() => {
if (!store.state.page.loading) {
store.dispatch('updateMobileNav', false);
clearInterval(watcher);
}
}, 100);
} else {
store.dispatch('updateMobileNav', false);
}
}
}
This doesn't specifically have to be for a mobile menu open/close. It can be used for anything in middleware that needs to wait for asyncData to resolve. Hope this can help anyone in the future!
Without reloading the whole page I need to reload the current route again (Only a component reload) in a vue app.
I am having a path in vue router like below,
{
path: "/dashboard",
name: "dashboard",
component: loadView("Dashboard"),
},
When user clicks on the Dashboard navigation item user will be redirected to the Dashboard page with vue router programmatic navigation
this.$router.push({ name: "dashboard" });
But when user already in the dashboard route and user clicks the Dashboard nav item again nothing happens. I think this is vue router's default behaviour. But I need to force reload the Dashboard component (Not to refresh the whole page).
I can't use beforeRouteUpdate since the router is not updated. Also I have tried the global before guards like beforeEach. But it is also not working.
How can I force reload the dashboard component without reloading the whole page?
It can be done in two ways.
1) Try doing vm.$forceUpdate(); as suggested here.
2) You can take the strategy of assigning keys to children, but whenever you want to re-render a component, you just update the key.
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
Every time that forceRerender is called, the prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and “reset” its state.
Not mentioned here, but as the offered solutions require a lot of additional work just to get the app to render correctly, which imo is a brittle solution.. we have just implemented another solution which works quite well..
Although it is a total hack.
if (this.$route.name === redirect.name) {
// this is a filthy hack - the vue router will not reload the current page and then have vue update the view.
// This hack routes to a generic page, then after this has happened the real redirect can happen
// It happens on most devices too fast to be noticed by the human eye, and in addition does not do a window
// redirect which breaks the mobile apps.
await this.$router.push({
name: RouteNames.ROUTE_REDIRECT_PLACEHOLDER
});
}
... now continue to do your normal redirect.
Essentially, redirect to a placeholder, await the response but then immediately continue to another page you actually wanted to move toward
I have an App.vue component where I want to load the currently logged in user. I also want to redirect the user when he\she tries to go the login route and the currently logged in user is already saved in context (he's already signed in).
// inside App.vue component
created() {
AuthService.getCurrentUser().then((user) => {
this.user = user;
});
}
I have a check in the created method of the login component for whether the currentUser is setted, but then when the user tries to go to the login page it might be possible that the the request for the current user is not finished.
My question is:
How do I wait for the data to load before the App.vue component loads?
I saw something like this:
waitForData: true,
data(transition) {
return AuthService.getCurrentUser().then(currentUser => {
transition.next({ currentUser });
});
}
which doesn't actually wait for the data to be loaded and the component loads anyway.
Edit: I'm aware of beforeRouteEnter but this is App.vue component which is a parent component of all components and not a route specific component.
I ran into a very similar problem and solved by adding a v-if on the element that wrapped the child component that depends on the loaded data. And then of course have an data property to control that v-if. If you don't have a wrapping element you can always use a template element to do the wrapping.
This is not the Vue way of doing things.
Use VUEX to store your currentUser object
Set up the Vuex getters in App.vue's computed section. Your template will be updated dynamically once the data is ready.
See the mapGetters section in this page. It works very well with the computed mechanism.
You can also use v-if in your relevant component, so that the component won't be created before your data is ready in VUEX.
If using vue-router, you can use the beforeRouteEnter guard to load data async, as described here: https://router.vuejs.org/en/advanced/data-fetching.html
One way I can think of is when the user is loaed in the App.vue component to check the current path and if it's \login to redirect.
Something like this:
created() {
AuthService.getCurrentUser()
.then(user => this.setCurrentUser(user))
.then(() => {
const { path } = this.$router.currentRoute;
if (path === '/login') {
this.$router.push('/');
}
});