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

Allow a user to read and write state on history entries (history.state) #2243

Open
crswll opened this issue Jun 5, 2018 · 19 comments
Open
Labels
feature request fixed on 4.x This issue has been already fixed on the v4 but exists in v3

Comments

@crswll
Copy link

crswll commented Jun 5, 2018

What problem does this feature solve?

Mobile apps that want to save scroll position for elements besides window.

This would also allow the user to save whatever they want to history state. A project I'm working on now needs to keep track of what's "focused" per page and this seems like the correct place for it. It could also be a nice place for temporary form data. I'm sure there's a lot of use cases outside of this.

What does the proposed API look like?

I'm still thinking about it, though I think someone else thinking about it would be better. :)

I checked out react-router a bit and it looks like we can make state part of the Location object, so we can set it in $router.push|replace({...}) or next({...}) calls. They use an additional argument but we might not need to?

I took a quick look at react-router and it seems to allow for this: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/history.md

Also, this seems to talk directly to the situation we're facing with scrolling:
https://reacttraining.com/react-router/web/guides/scroll-restoration

Also some additional reading: https://github.com/ReactTraining/history#properties

Hopefully this is useful and doesn't seem like useless rambling.

@jnields
Copy link

jnields commented Sep 4, 2018

My app relies on being able to associate extra state in the history that isn't in the URL and this prevents me from being able to use vue-router

react-router uses history, which does something like:

history.replaceState(
  {  key: createKey(),  state: 'user-provided state' },
  null,
);

and provides access to location something like:

const location = {
  pathname: window.location.pathname,
  search: window.location.search,
  hash: window.location.hash,
  state: history.state.state,
}

users aren't expected to access history.state directly, though I suppose you could

@719media
Copy link

719media commented Apr 22, 2019

This would be extremely useful as a way to pass data to the scrollBehavior handler to allow different router-links that share the same to= string/object so that they can differ in regards to scrollBehavior.

@posva posva changed the title Allow a user to read and write state on history entries Allow a user to read and write state on history entries (history.state) Oct 1, 2019
@J-Rojas
Copy link

J-Rojas commented Nov 9, 2019

To workaround this, I currently have to track changes to the $route globally, inspect and copy values from window.history.state, then use window.history.replaceState() to push the new application state into the window history. It would be helpful if this is done within the Router framework.

@jimut
Copy link

jimut commented Feb 24, 2020

There are some scenarios where this is impossible to workaround. Our app has some popups that need to close on the device back press. Right now we add a query in the URL to do that but as the number of popups increases, our URLs would become nasty.

The one and the only way to show a popup like a native app is to store its state in the history object. This way we can push the same URL and store a flag for the popup in the history so that on the device back it can be used to close the popup again. This not only solves the problem of closing a popup on the device back it will also persist the state on page refresh so that the popup can open up again without relying on the URL.

There are cases where opening a popup with a URL change is the correct way but there are other cases too where a URL change is completely unnecessary.

Take a look at this page. Open it in a mobile and click on view similar.

They were able to do it because they used React.

And we are not able to do it because of the default behaviour of vue-router.

@posva
Copy link
Member

posva commented Feb 24, 2020

FYI since 3.1.4 you can overwrite the history state using replaceState and vue router won't replace it anymore.
This feature request should go through the RFC process to go through the thinking process of pros, cons, alternatives and gather more feedback.

@jimut
Copy link

jimut commented Feb 25, 2020

Yeah, I can see that router.replace doesn't override the state but it will not help to solve this use case.

The only half ended solution that I can find is to use history.pushState directly to push the same route with a flag in the state when the popup is opened so that it gets closed on device back.

But still, the problem of persisting the popup state on page refresh will not get solved because the function setupScroll is called on initialization of vue-router and it replaces the state like this.

window.history.replaceState({ key: getStateKey() }, '', absolutePath)

Maybe here if you could have written the logic of not replacing the state like this.

window.history.replaceState({ ...window.history.state, key: getStateKey() }, '', absolutePath)

We could have implemented a full-fledged solution by now.

@posva
Copy link
Member

posva commented Feb 25, 2020

@jimut I will definitely take a PR to improve that! It's part of #3006
It shouldn't be a very hard PR to make. It does require an e2e test?

edit: fixed that one and released v3.1.6

@jimut
Copy link

jimut commented Feb 26, 2020

Thank you so much for the quick fix.

Hopefully in 3.2 we will be able to pass state objects in push/replace methods and get to from the route object maybe.

@posva
Copy link
Member

posva commented Feb 26, 2020

As said, passing state through push/replace needs to go through the RFC process but right now you can just do

await router.push('/somewhere')
history.replaceState({ ...history.state, ...newState }, '')

To add state to the current history entry after a navigation

@posva posva added fixed on 4.x This issue has been already fixed on the v4 but exists in v3 and removed fixed on 4.x This issue has been already fixed on the v4 but exists in v3 labels Mar 28, 2020
@vuejs vuejs deleted a comment from yahao87 May 14, 2020
@posva posva added the needs RFC This feature request needs to go through the RFC process to gather more information label Sep 1, 2020
@vuejs vuejs deleted a comment from shivamgupta211 Nov 14, 2020
@ZachHaber
Copy link

I'd like to throw in my thoughts on this as you mentioned this is an RFC now. History state is a fantastic feature that would really improve vue router. I came across this issue when I ended up spending a few hours working on figuring out a way to get a workflow done without it.

I was trying to figure out a way to easily and consistently make it so I could always return to the same page the user left from. (or the default if the user navigated directly to the Other route).

<v-btn :to="{name: 'Other', state: {prevRoute: $route.fullPath}}">btn</v-btn>

This would be paired with a corresponding button on the other page. That would've been all the code I needed to make the feature work the way I wanted and was the first thing I tried.

<v-btn :to="$route.state?.prevRoute || '/overview/main' ">Return</v-btn>

After finding this thread, I could've done the same awaiting programatic navigation and then a manual history.replaceState, but that would've made it so that the link to go to Other wouldn't be a proper link i.e. user wouldn't be able to see the url ahead of time.


Instead I spent a couple hours searching through the api and trying things out ineffectively. Like the meta values have to be applied via the route itself, which meant it wasn't helpful here. I could've used query params, but I didn't want to crowd the ending url with this metadata as it is more of a convenience feature and might be confusing for users.

What I ended up with was setting up a beforeRouteEnter guard on the Other component where I set a data property via the callback on next as was pointed out https://stackoverflow.com/a/53789212/13175138.

Which meant that I had to check to make sure that navigation to that route was coming from the overview page and then set the property only in that instance. And of course, if the page is refreshed, that data is lost since it's store in JS.

What I ended up with was:

  beforeRouteEnter(to, from, next) {
    next((vm) => {
      if (from.name === 'Overview') {
        vm.prevRoute = from.fullPath;
      }
    });
  },

In react-router, the API already exists where you can just put a state on a Link and it works: https://reactrouter.com/web/api/Link/to-object.

<Link to={{pathname: '/other', state: {prevRoute: location.pathname}}} />

I'm well aware that vue router and react router are very different in their philosophies, but I still feel like this should be standard in any history api based router due to both the convenience and the ability for state to be tied to a particular location in the browser history. The latter definitively adding a ton of possibilities that would otherwise require much less clean approaches.

@posva
Copy link
Member

posva commented Oct 15, 2021

Published an RFC for this: vuejs/rfcs#400

@posva posva added feature request fixed on 4.x This issue has been already fixed on the v4 but exists in v3 and removed discussion needs RFC This feature request needs to go through the RFC process to gather more information labels May 21, 2022
@zenflow
Copy link

zenflow commented Jul 8, 2022

Fixed in version 4.1!!!

@vincerubinetti
Copy link

Just tried it in 4.1. We can now set state in push/replace, but it seems that that data is not attached to the vue-router location objects. We have to get it from window.history directly, which may necessitate jumping through hoops in certain situations. I hope this is planned to be added soon. React router has had both of these features since at least v3 (~5 years old).

@leegee
Copy link

leegee commented May 18, 2023

Am I right in thinking there is no way to have state specified once and applied to all navigations?

@AlejandroAkbal
Copy link

I hope this issue gets more traction because I am having quite a difficult time saving & restoring state

@ZachHaber
Copy link

I made a fork off of the Vue 2 router to solve this issue for me: https://github.com/ZachHaber/vue-router-state. It allows passing in state as a property on RouteLinks, push, and replace. You can get the state off of the $route object (and via the composition api 'vue-router-2-state/composables'). It should be a drop-in replacement with better typescript support than the original.

@leegee
Copy link

leegee commented Nov 30, 2023

I made a fork off of the Vue 2 router to solve this issue for me: https://github.com/ZachHaber/vue-router-state. It allows passing in state as a property on RouteLinks, push, and replace. You can get the state off of the $route object (and via the composition api 'vue-router-2-state/composables'). It should be a drop-in replacement with better typescript support than the original.

Interesting, thanks for sharing. Maybe put the docs in the README.md?

@ZachHaber
Copy link

Interesting, thanks for sharing. Maybe put the docs in the README.md?

The docs on it are live and updated a little with the composables (from the vue-router 4 docs, as those still apply) as well as the history state parts.

@Cheaterman
Copy link

Fixed in version 4.1!!!

So I guess not really? As per the RFC:

There are plans for a similar API for params, query, and state 🙂

Currently the e2e example sets up a ref() to history.state but surely this should be provided by vue-router? That would also give the framework an opportunity to remove (hide) internal state from users, which is probably desirable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request fixed on 4.x This issue has been already fixed on the v4 but exists in v3
Projects
None yet
Development

No branches or pull requests