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

Pass globals compatible with Setup Stores #1784

Closed
posva opened this issue Nov 8, 2022 · 16 comments
Closed

Pass globals compatible with Setup Stores #1784

posva opened this issue Nov 8, 2022 · 16 comments

Comments

@posva
Copy link
Member

posva commented Nov 8, 2022

What problem is this solving

Currently we can add globals to options stores with

pinia.use(() => ({ router }))

But this only affect option stores

Proposed solution

Similar to app.config.globalProperties: https://vuejs.org/api/application.html#app-config-globalproperties

pinia.globals.router = router

import type { Router } from 'vue-router'

// typing the globals
declare module 'pinia' {
  export interface PiniaGlobals {
    router: Router
  }
}

Then set up stores could receive these properties as an argument:

defineStore('store', ({ router }) => {
// ...
})

Option stores could still use the properties through this.

Describe alternatives you've considered

  • Directly adding properties to pinia and use getActivePinia(). This also works with TypeScript, it not semantic though
  • Ideally, users should be able to call useRouter() and similar composables within setup stores but this requires a way for libraries to set the active Vue App, so this requires Vue to implement some kind of setCurrentApp(app) (needs PR and discussion)
@karmats
Copy link

karmats commented Dec 1, 2022

This would be fantastic! Right now I'm defining the this-type (Store) in my action functions to be able to access my plugins.

pinia.use(() => ({
        myPlugin: { someFunction: () => console.log('Hello world') },
      }))

...

function doStuff (this: Store) {
  this.myPlugin.someFunction();
}

@the94air
Copy link

It's pretty hard to use Pinia setup API without being able to pass in globals through plugins. Passing them through actions from components generates antipatterns logic. This is quite an important feature to see in Pinia.

@the94air
Copy link

the94air commented Dec 26, 2022

If you really want to use the Pinia setup API like I am, the only workaround I find is to wrap the store with another function and then pass the context from any component. Although I don't know any bad sides about this approach yet

// foo.vue
const { submitForm, etc } = useBarStore(customContext)();

// stores/bar.js
export const useBarStore = (customContext) =>
  defineStore("...", () => {
    // ...
  });

Copy link
Member Author

posva commented Dec 27, 2022

There are discussions to make it possible to make useRouter() and others just work within store definitions but it's still in discussion

BTW doing this

function useBarStore(context) {
  return defineStore('...', => {
  })()
}

should be avoided as you are not supposed to define a store with the same id multiple times.

posva added a commit to vuejs/core that referenced this issue Jan 2, 2023
Allows accessing globals provided at the app level with `app.provide()`
via a regular `inject()` call as long as you have access to the
application. Useful for Pinia, vue-router, Nuxt, Quasar, and other
advanced use cases.

- vuejs/pinia#1784
@posva
Copy link
Member Author

posva commented Feb 11, 2023

A workaround to have globals used within setup stores is to pick them up from the app (if possible). here is an example with the router:

import { getActivePinia } from 'pinia'

export function useRouter() {
  return getActivePinia()?._a.config.globalProperties.$router!
}

export function useRoute() {
  return getActivePinia()?._a.config.globalProperties.$route!
}

These functions should work properly within a setup stores as if you were callig the original router composables within a script setup

posva added a commit to vuejs/core that referenced this issue Mar 9, 2023
Allows accessing globals provided at the app level with `app.provide()`
via a regular `inject()` call as long as you have access to the
application. Useful for Pinia, vue-router, Nuxt, Quasar, and other
advanced use cases.

- vuejs/pinia#1784
yyx990803 pushed a commit to vuejs/core that referenced this issue Apr 5, 2023
Allows accessing globals provided at the app level with `app.provide()`
via a regular `inject()` call as long as you have access to the
application. Useful for Pinia, vue-router, Nuxt, Quasar, and other
advanced use cases.

- vuejs/pinia#1784
posva added a commit that referenced this issue May 17, 2023
@posva posva closed this as completed in 6a71019 May 17, 2023
@aentwist
Copy link

aentwist commented May 19, 2023

There is a very annoying lack of documentation about this for Vue Router, and in a lesser way for Pinia. This issue is extremely informative. I am guessing that there is some invisible context that useRoute needs that definePinia does not have even though it is called from said context?

Assuming this solution was implemented - is router.currentRoute reactive here as it would be with useRoute?

defineStore('store', ({ router }) => {
// ...
})

If not, thanks for the amazing workaround (which is reactive?).

There are discussions to make it possible to make useRouter() and others just work within store definitions but it's still in discussion

If discussion has made it into writing would you please include a link to it?

Edit: Upgraded Pinia to 3.3.4 and now useRoute seems to be working from within Pinia. What the heck. Now I'm completely lost.

Copy link
Member Author

posva commented May 20, 2023

Yes, you can just call useRouter() within a setup store now! There is a note about this in docs

@aentwist
Copy link

Thank you so much for this! I'll add our user story for some closure.

We believe in the rationale behind the composition API and have fully migrated from the options API. We avoid Vuex stores since, although convenient for migration, they are based on a legacy tool and provide no benefit. We use setup stores as their composition API syntax reduces required knowledge.

We want to use the URL as a backend for state. That is, to have URL query parameters be the single source of truth for the state they represent. This avoids having one state for the app (variables) and another for the URL (query params) and having to keep them in sync (for example by reading initial values from the URL and adding watchers to variables that update the URL). All route interaction can easily be abstracted into store computed variables, for easier conceptualization and better maintainability.

Even if this was possible before (we struggled), it is a huge improvement to maintainability imo.

@leochen-g
Copy link

Yes, you can just call useRouter() within a setup store now! There is a note about this in docs

Hello, in my project I have already used the latest version, but when using const route = useRoute(); I still get the prompt inject() can only be used inside setup() or functional components.

my code

defineStore('member', () => {

   function logout() {
       const $route = useRoute();
       if ($route.path.includes('/member/')) {
        window.location.href = `${lang}`;
      } else {
        window.location.reload();
      }
  }
})

vue: 3.3.4
pinia: 2.1.4
vue-router:4.1.6

Copy link
Member Author

posva commented Jun 27, 2023

It must be outside of logout. Think of the setup store as a component script setup: you wouldn’t write useRoute within a function either

@leochen-g
Copy link

It must be outside of logout. Think of the setup store as a component script setup: you wouldn’t write useRoute within a function either

Thank you very much for your reply, I tried to use const $route = useRoute(); outside of logout and defineStore, but it would give a warning that inject() can only be used inside setup() or functional components.

Copy link
Member Author

posva commented Jun 27, 2023

It's inside defineStore()...

defineStore('...', () => {
  const route = useRoute()
})

Like in a script setup component

@mrleblanc101
Copy link

mrleblanc101 commented Jun 28, 2023

In Nuxt I get this error when doing this:

[nuxt] Calling useRoute within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.

I'm just trying to do a getter like so:

const route = useRoute();

function currentTopic() {
    const slug = route.params.topic || route.matched[0]?.meta?.topic;
    return topics.value.find((t) => t.slug === slug);
}

return {
    currentTopic,
}

@mrleblanc101
Copy link

mrleblanc101 commented Jun 28, 2023

My bad, I change my regular pinia store to a setup store to have access to useRoute and accidentaly change my getters to functions instead of computed. The warning disappeared once I made this change:

const route = useRoute();

const currentTopic = computed(() => {
    const slug = route.params.topic || route.matched[0]?.meta?.topic;
    return topics.value.find((t) => t.slug === slug);
});

return {
    currentTopic,
}

@gscpw
Copy link

gscpw commented Feb 15, 2024

I have a plugin that adds ssrContext to each store. How can I access this property in a composition-style store? Since composition-style store does not have access to this, how do I get reference to custom properties?

@dsl101
Copy link

dsl101 commented Feb 16, 2024

I'm really confused by @mrleblanc101 and the example here. In this code using pinia 2.1.7:

import { useRoute } from 'vue-router'

export const useAppStore = defineStore('app', () => {
  const route1 = useRoute()
  const toolbarLabel = computed(() => {
    const route2 = useRoute()
    console.log('computed route1:', route1)
    console.log('computed route2:', route2)

I see:

computed route1: undefined
computed route2: Proxy { <target>: {…}, <handler>: {…} }

in the console. I see so many examples around where useRoute() is called at the top of a setup store, but it just doesn't work for me. What am I missing?

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

8 participants