Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add afterRouteEnter guard #2840

Closed
ykushev opened this issue Jul 4, 2019 · 10 comments
Closed

Add afterRouteEnter guard #2840

ykushev opened this issue Jul 4, 2019 · 10 comments

Comments

@ykushev
Copy link

ykushev commented Jul 4, 2019

What problem does this feature solve?

I want execute one function in component after each routing (enter route, reused in the new route)

What does the proposed API look like?

variant 1:

beforeRouteUpdate (to, from, next) {
        console.log('page category beforeRouteUpdate');
        next(() => {
            // this don't calling
            console.log('page category beforeRouteUpdate next');
            this.$store.dispatch('catalog/exec');
        });
    },

variant 2: add hook named "afterRouteUpdate" that will calling after we enter in route and after each time we navigating in this route (foo/2 -> foo/3)

@posva
Copy link
Member

posva commented Jul 4, 2019

I think adding an after guard in components is not a good idea because it adds more cognitive load for people learning and because it's something that most of the time can be refactored in a method that is called in both beforeRouteEnter and beforeRouteUpdate and it still feels natural.

The only thing is, other nested guards can be executed and reject navigation and this can cause some code executed in a beforeRouteUpdate to be executed after navigation was confirmed even though a child rejected the navigation.

So, I'm interested about use cases for an in-component after hook

Regarding the callback passed to next in beforeRouteUpdate, see #1582

@ykushev
Copy link
Author

ykushev commented Jul 4, 2019

Oh, my apologies. did not see this bug.
Yes, that decision with

beforeRouteUpdate (to, from, next) {
        console.log('page category beforeRouteUpdate');
        next();
        this.$store.dispatch('catalog/exec');
    },

Looks like working (at least a quick test showed so).

The case with the in-component after hook is:
The whole site uses fairly simple routing, but in one place (online store), or rather on one page. It is necessary to add the parameters after the router has been worked out.
Examples:
/aaa/
need to replace with
/aaa/?limit=30&page=1&sort=price&storageIds=any&view=table

Yes, these parameters can be calculated even on the server or in asyncData and implement a 302 redirect ...
But, I think that such a number of redirects (and it is on all pages) will have a bad effect on the SEO.
Especially given the fact that all internal links must looks like /aaa/ (without parameters)

You ask the purpose of these parameters?
It’s very simple - if a client wants to copy this link and send it to a friend, so that a friend has a page with exactly the same data as the client has.

So what do we have?
window.history.replaceState({}, '', encodeURI(url));
Which we need to call after the transition to this URL.
In this case, we need a this to access the store, because all information for calculating these parameters is stored there.
And do not advise me import store - this create new instance of store.
And i see #2624 and wait for it too.

Well, in general, I was very surprised that the component cannot be set to callback for execution after.
Yes, you can set in next in beforeRouteEnter, but this only works on the first transition, and I need every time (and the first one too).
Well, given the names of these hooks - we write in the function before what is executed after the transition ...
As a result, the code is not entirely logical and not easily readable (in-component)

Oh, and I forgot - the main problem was that my function of replacing parameters worked before the transition of the router was completed
And after that, the router itself changed the URL to the one that was in his memory.
Therefore, I need a place in which the router is guaranteed to complete the transition to a specific page.

@posva
Copy link
Member

posva commented Jul 4, 2019

Oh, my apologies. did not see this bug.

No, no, it's a caveat of the solution I described about putting the afterRouteEnter code inside the beforeRouteEnter hook

The whole site uses fairly simple routing, but in one place (online store), or rather on one page. It is necessary to add the parameters after the router has been worked out.
Examples:
/aaa/
need to replace with
/aaa/?limit=30&page=1&sort=price&storageIds=any&view=table

But then that should be doable in a global afterEach that checks against the route itself of a meta attribute added in the route record.
To access the store, you can avoid the function to create a new one by having an optional parameter that returns the lastly created store but if you are using Nuxt, I don't know if you have a context parameter somewhere accessible

But is there any reason to access the component in that after hook? If not, a global hook is the right place

About the query parameters, I think it would make more sense to allow them not to exist in the URL by providing default values. Something like using computed properties:

computed: {
  limit: {
    get: ({ $route }) => Number($route.query.limit) || 30,
    set (limit) {
      this.$router.push({ query: { ...this.$route.query, limit })
    }
  }
}

@ykushev
Copy link
Author

ykushev commented Jul 5, 2019

Nuxt... no, i don't using this. I have custom ssr solution from one example with koa.

But is there any reason to access the component in that after hook? If not, a global hook is the right place

Yes! This logic with parameters applies only to one route. If i write everything in the global after hook, then its code will sooner or later become huge.

With query parameters, yes, I do:
default values ​​< client values ​​from session < passed values

These values ​​are stored in the store because I have a huge logic of navigating this route, receiving data, many more related entities (bread crumbs), filters, and so on.
These parameters are calculated there because they must be calculated before receiving the data and because on the server they must be calculated using the same algorithm (single). And respectively stored in the store. And then from there must be taken and added missing to the url.

@posva
Copy link
Member

posva commented Jul 5, 2019

Yes! This logic with parameters applies only to one route. If i write everything in the global after hook, then its code will sooner or later become huge.

So there is no need to access the component itself. A global after is perfectly fine, you can filter inside it:

const routes = [
{
  path: '/...',
  // other options
  meta: { queryRedirect: { ... } }
}
]
router.afterEach((to, from, next) => {
  // could also be a check about the name
  if (!to.meta.queryRedirect) return next()
  if (!isQueryPresent(to.query)) next({ query: to.meta.queryRedirect }) // using spread to create a copy
  else next()
})

Regarding where to store the data, I would still store the data in the url and let the store retrieve through a getter so there is only one source of truth

I still believe having a default value instead of creating a redirection is an easier way to solve the problem

@ykushev
Copy link
Author

ykushev commented Jul 5, 2019

I need access to component itself for access store.

About global afterEach:

  1. is not a good place for logic one route.
  2. in router i can't access store.

My asyncData takes 2 params: store and route there 1 place where i can access to both.
The 2nd place - component after mounting.

Let me try to describe the algorithm as it works in my project:
On server:

  1. inside the router.onReady made request to get the client session parameters and put it in store.
  2. The router is executed and determines the specific route.
  3. The asyncData function is called from the component, inside of which:
    3.1. takes default parameters from the store (the component does not yet exist), takes the client session parameters from the store and those that were transferred to the router, of these three new parameters are determined.
    3.2 request for data with these parameters
  4. Store is transferred to the client

the following happens on the client:
5. a certain route and a component match
6. if the transition is made on the client, asyncData is called from the component and clauses 3.1 - 3.2 are executed
7. transition completes

I still believe having a default value instead of creating a redirection is an easier way to solve the problem

Default value that you write - creating redirection too. And several default values will create more redirects.
Therefore i have one function calling "parseUrl" which parsing string and adds or replaces parameters. And after that replace url. it's not redirect. if i will use router.push - i will several times calling asyncData and get many, very many data about page (my server is not connected to the database, it receives data from api)

@posva
Copy link
Member

posva commented Jul 5, 2019

I need access to component itself for access store.

But that's my point, the store can be accessed globally. For example in Nuxt, you have access to a context in multiple places and that's how you can access both the store and router in plugins. So, do you need access to anything else beyond the store?

Thanks for sharing the way you do it. It's okay to have an afterEach that applies to a few routes or one route only. At the end, it all comes to not having access to the store but even in SSR, you should be able to have access to the store and the router: https://ssr.vuejs.org/guide/data.html#data-store

@ykushev
Copy link
Author

ykushev commented Jul 8, 2019

Ok, i'll try yo do with afterEach and try to make my store accessible from everywhere.

But, by the way, I have a project - a template of what I described above. I can add a little of it to repeat the problem and write a link to the repository

@posva
Copy link
Member

posva commented Jul 8, 2019

But, by the way, I have a project - a template of what I described above. I can add a little of it to repeat the problem and write a link to the repository

I appreciate it but there is no need since there isn't a bug to check

@posva
Copy link
Member

posva commented Apr 20, 2020

See vuejs/rfcs#150

@posva posva closed this as completed Apr 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants