Skip to content

history is undefined when the router.replace is called before injecting the router into Vue instance #795

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

Closed
theshem opened this issue Oct 17, 2016 · 12 comments

Comments

@theshem
Copy link

theshem commented Oct 17, 2016

I need to call router.replace before injecting the router into the Vue instance. But can't figure out why history property is undefined in that situation:

VueRouter.prototype.replace = function replace (location) {
  this.history.replace(location) // TypeError: Cannot read property 'replace' of undefined
}

Here is the online example (check the console) and the code snippet:

// 0. If using a module system, call Vue.use(VueRouter)

// 1. Define route components.
// These can be imported from other files
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. Define some routes
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = new VueRouter({
  routes
})

try {
    router.replace('/bar');
} catch(e) {
    console.log(e); // TypeError: Cannot read property 'replace' of undefined
}

// 4. Create and mount the root instance.
// Make sure to inject the router with the router option to make the
// whole app router-aware.
const app = new Vue({
  router
}).$mount('#app')

// Now the app has started!
@theshem theshem changed the title history is undefined when the router.replace is called before injecting the router into Vue instace history is undefined when the router.replace is called before injecting the router into Vue instace Oct 17, 2016
@theshem theshem changed the title history is undefined when the router.replace is called before injecting the router into Vue instace history is undefined when the router.replace is called before injecting the router into Vue instance Oct 17, 2016
@posva
Copy link
Member

posva commented Oct 17, 2016

You should do it after injecting the router into a Vue instance. I think this is intended, but would like some input from @fnlctrl and/or @yyx990803

@fnlctrl
Copy link
Member

fnlctrl commented Oct 17, 2016

That this.history requires the vue instance the router is injected to be created (to call router.init() which sets up this.history), which doesn't exist yet when you call router.replace() before injecting. Unfortunately there's no way around that.

However, if you only need router.replace functionality, you can directly call the native browser apis that are called by vue-router under the hood:

For history mode, it's simply window.history.replaceState('/').
For hash mode, it's window.location.replace('#/')

@fnlctrl
Copy link
Member

fnlctrl commented Oct 17, 2016

@HashemQolami Did it solve your problem?

@theshem
Copy link
Author

theshem commented Oct 17, 2016

@fnlctrl hmm.. that makes sense. I guess you meant the window.history.replaceState().

I was under the assumption that this points to the created VueRouter instance, still can't find out how it points to the Vue instance. Any hints are appreciated.

@fnlctrl
Copy link
Member

fnlctrl commented Oct 17, 2016

I guess you meant the window.history.replaceState().

Yeah.. fixed it.

still can't find out how it points to the Vue instance

I was wrong about it, and you probably missed my updated comment above 😂 .

@theshem
Copy link
Author

theshem commented Oct 17, 2016

@fnlctrl

That this.history requires the vue instance the router is injected to be created (to call router.init() which sets up this.history) [...]

Does setting up the this.history require the presence of the vue component instance? Or that is a design decision?

I am asking as it seems that the HTML5History just needs the router.options to be instantiated. Actually it is the this.$options.router.options.base where this is the vue component instance. Or the base property varies? (belonging to each vue component?)

@LinusBorg
Copy link
Member

LinusBorg commented Oct 17, 2016

Does setting up the this.history require the presence of the vue component instance? Or that is a design decision?

It's nessessary by the fundamentals of the implemented transitioning process.

When you do router.replace() you trigger a route transition, which relies upon the router-view component to initialize the right components - which can't happen before your app has started.

Also, things like the in-component beforeRouteLeave hook are executed by History, and won't have access to the vm as this because, again, the component can't be initialized by router-view without the application being started.

etc. pp.

@fnlctrl
Copy link
Member

fnlctrl commented Oct 17, 2016

I'm looking into this, looks like basic functionalities like replace/push can really be performed before vue instance initialization, with some changes to the source.

@LinusBorg
Copy link
Member

LinusBorg commented Oct 17, 2016

Not sure the options that could be available exceed what window.history offers, but happy to be proven wrong :)

@fnlctrl
Copy link
Member

fnlctrl commented Oct 17, 2016

@LinusBorg I didn't mean you're wrong, sorry if I made you think that... Your explanation was perfect, and I'm just thinking that it may be an improvement to allow users to do basic replace/push before initializing root vue instance. And I just got a proof-of-concept working that passed all tests, will submit a PR shortly. ;)

@LinusBorg
Copy link
Member

LinusBorg commented Oct 17, 2016

I didn't take it that way, don't worry ;) I hope you're successful

fnlctrl added a commit that referenced this issue Oct 17, 2016
…795)

Previously it would throw errors.

Allowed methods: push, replace, go, back, forward

Reason:
These methods only try to manipulate browser history, and should work even when root vue instance has not been created.

Common use case:
Call `router.replace()` to set browser url state. This action doesn't require vue's existence, so it shouldn't throw an error when called before vue's instantiation.
 Otherwise, users may need to check router's actual mode after fallback (history or hash) and then call `history.replaceState` or `location.replace` accordingly.
@yyx990803
Copy link
Member

yyx990803 commented Nov 13, 2016

fixed via 83418bc

yyx990803 pushed a commit that referenced this issue Nov 13, 2016
…795)

Previously it would throw errors.

Allowed methods: push, replace, go, back, forward

Reason:
These methods only try to manipulate browser history, and should work even when root vue instance has not been created.

Common use case:
Call `router.replace()` to set browser url state. This action doesn't require vue's existence, so it shouldn't throw an error when called before vue's instantiation.
 Otherwise, users may need to check router's actual mode after fallback (history or hash) and then call `history.replaceState` or `location.replace` accordingly.
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

5 participants