davestewart / vuex-pathify

Vue / Vuex plugin providing a unified path syntax to Vuex stores
https://davestewart.github.io/vuex-pathify
MIT License
1.37k stars 57 forks source link

Unable to expand wildcard path #53

Open arielnmz opened 5 years ago

arielnmz commented 5 years ago

After upgrading a working project from nuxt 1.4 to 2.5 I'm getting this error

[Vuex Pathify] Unable to expand wildcard path ...:

  • The usual reason for this is that the router was set up before the store
  • Make sure the store is imported before the router, then reload

This is how I added pathify to the vuex plugins:

import pathify from 'vuex-pathify'
...
export const plugins = [
  createPersistedState({
    key: 'myapp',
    paths: ['session',]
  }),
  pathify.plugin,
]

I'm using nuxt in spa mode

davestewart commented 5 years ago

Hmmm. I can't say why that's not working.

Can you post in the Nuxt issues and see what comes up?

arielnmz commented 5 years ago

I'm trying to replicate it with a clean, new nuxt app.

arielnmz commented 5 years ago

I've created a new nuxt app and set up a simple store with the following structure:

export const state = () => ({
  ui: {
    sub: {
      attr: false,
      ...
    },
  },
})

And I'm trying to use it like this in my component

  export default {
    name: 'MyComponent',
    computed: {
      ...sync('ui@sub.*')
    }
  }

And this is the error I get:

Unable to expand wildcard path 'ui@sub.*':

Changing it to ui@sub makes it work again

It works in version 1.1.3 with nuxt@^1.4.5, and updating to nuxt 2 breaks it. Updating to vuex-pathify@^1.2.0 doesn't help btw.

What could nuxt be doing that breaks this?

davestewart commented 5 years ago

OK, if you share the nuxt project I'll load it up and take a look.

davestewart commented 5 years ago

Is it related to this at all?

https://github.com/davestewart/vuex-pathify/pull/46

Apparently Nuxt 2.x was doing some weird things. I didn't check, just trusted the contributor.

arielnmz commented 5 years ago

Finally, I was able to look closer into this. There are tons of scenarios (nuxt in ssr mode, client mode, loading pathify as a client plugin, as aserver plugin, as a vuex plugin). I'll explain a simple and the most common case, assuming we are using nuxt@^2.5.0 in ssr (universal) mode, and pathify@^1.2.0:

If I try to e.g. sync a path like 'ui@sub.*', pathify tries to expand it via expandSync and fails because vuex.store.state is null at that moment. The reason a path like ui@sub doesn't fail is because pathify is not trying to expand it, instead it just calls getOne, syncOne, etc., which don't need the state to be initialized (they just map the path to the corresponding getter/setter). The problem with the resolver function is that it checks if the state is initialized at the moment of expanding the path, and since it is null, it just returns '' (an empty string), and in the next stage it tries to reduce it, triggering an exception because a string doesn't have the reduce method:

TypeError: paths.reduce is not a function

/**
   * Utility function to expand wildcard path for sync()
   *
   * @param   {string}        path        wildcard path
   * @param   {object}        state       state hash
   * @returns {array|string}
   */
  function expandSync (path, state) {
    if (!init(path, state)) {
      return ''
    }
    return resolveStates(path, state)
  }

Then:

/**
   * Helper function to convert an array of paths to a hash
   *
   * Uses the last path segment as the key
   *
   * @param   {string[]}  paths   An array of paths to convert to a hash
   * @returns {object}            A hash of paths
   */
  function makePathsHash (paths) {
    return paths.reduce(function (paths, path) {
      var key = path.match(/\w+$/);
      paths[key] = path;
      return paths
    }, {})
  }

The first thing that comes to mind is to return [] instead of '', this prevents pathify from crashing but doesn't change the fact that the store wasn't initialized and therefore can't even expand the path.

TL;DR the problem is Nuxt trying to render the routes before initializing the store. Can somebody replicate a working scenario? In the meantime, the "solution" would be to avoid using wildcards.

arielnmz commented 5 years ago

Going a step further, this seems to happen only when pathify is used in a layout or any component in a layout

https://codesandbox.io/s/ojqkwonl7y

arielnmz commented 5 years ago

Bug report in nuxt's github https://github.com/nuxt/nuxt.js/issues/5405

davestewart commented 5 years ago

Hey, thanks for investigating!

I know it's not fun when the codebase is not your own.

Out of interest (and I assume you used the source) how did you find navigating around the source?

Did it seem logical?

arielnmz commented 5 years ago

I actually just put some breakpoints in the bundled script to find what's it doing, but I'm seeing the source now, and yes, I can say it's pretty well organized, I quickly found my way around. Ty for your time

davestewart commented 5 years ago

FYI, I found a really cool Chrome extension the other day called OctoLinker which adds live links to GitHub repos:

It's great for this kind of thing!

arielnmz commented 5 years ago

Looks great! Im going to give it a try, ty

chiboreache commented 5 years ago

2.6.1

wildcard error still here

davestewart commented 5 years ago

Nuxt 2.6.1 ?

chiboreache commented 5 years ago

Yep

Screenshot_20190408_172807

I assume this is because of core-js3, which is nuxt roll over after 2.4.5 (core-js2) release

davestewart commented 5 years ago

Ah I see.

So you mean with the VP 1.2.2 update, Nuxt still has its errors.

You don't mean that Nuxt have released something, but it didn't fix it.

chiboreache commented 5 years ago

So you mean with the VP 1.2.2 update, Nuxt still has its errors.

Right  : )

Screenshot_20190408_180750

Even after reverting back to 2 version

Since core-js@2 and core-js@3 are both supported from babel 7.4.0, Starting from 2.6 Nuxt supports both versions (#5411). It is highly advised to stick with 2 (default) unless having a special dependency that needs 3. (Instructions for core-js@2 are not required but recommended) https://github.com/nuxt/nuxt.js/releases

it doesn't work ~_~

yowzadave commented 5 years ago

I see this issue with just a vanilla vue-cli app when I attempt to use wildcard expansion—no Nuxt, not even vue-router. See this sample repo:

https://github.com/yowzadave/vuex-pathify-test

in the "HelloWorld.vue" component (https://github.com/yowzadave/vuex-pathify-test/blob/master/src/components/HelloWorld.vue), the wildcard get fails with the above error. The non-wildcard get (commented out here) works fine.

VesterDe commented 5 years ago

I believe I'm having a related issue.

The error happens if I import my router into a module file in any way. I suppose it's because the store theoretically isn't initialised in this file, it's just a file that exports one of the modules of the state. I don't (yet) do anything with the router... Just importing it causes the error.

Is there any way to get it to work?

My current workaround is to do the things in components that I would rather do in actions.

Thanks.

davestewart commented 5 years ago

Hi @VesterDe,

This is probably an order-of-import issue.

As soon as you import your router file, webpack will then try to import everything that is included in that file, so you end up with a race condition because suddenly your components are being imported before the store they depend on.

You'll have to think of a way to allow the store to finish its setup before using the router, or at least the routes and the dependent components it depends on. Consider adding routes dynamically, lazy-loading of routes or something like this:

// helper
function router () {
  return require('/router').default
}

// use router in an action
const actions {
  doSomething () {
    router().whatever()
  }
}

Let me know how you get on :)

VesterDe commented 5 years ago

Thanks for the feedback, it seemed like what you describe to me too.

I've now solved it the way you recommended, although I'm not crazy about how it looks...

I do prefer to handle the redirect inside actions though, so I'll keep it.

For anyone wondering how exactly it looks for me, it's:

const loadRouter = () => {
  return require("@plugins/router").default
};

// Inside my login action
let router = loadRouter();
      if(router.app._route.query.loginRedirect){
        router.push(router.app._route.query.loginRedirect);
      }
davestewart commented 5 years ago

Another option would be moving all that code to a helper in the router folder, then you only need do:

import { isLoggingIn } from '@plugins/router'

const actions = {
  login () {
    if(!isLoggingIn()) {
      // your code
    }
  }
}

Because isLoggingIn() (or whatever you want to call it) is itself a function, it won't reference the router until you use it, so won't suffer the same order-of-import issues you were previously.

Also, if you are doing everything with the URL, there's nothing to stop you parsing the URL directly with location.search