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

fix: throw when a store is used outside of a Nuxt-aware context. #2857

Open
wants to merge 3 commits into
base: v2
Choose a base branch
from

Conversation

pguilbert
Copy link

@pguilbert pguilbert commented Dec 11, 2024

Context

The Pinia documentation states the following:

Make sure all of your useStore() calls appear before any await. Otherwise, this could lead to using the wrong Pinia instance in SSR apps: pinia.vuejs.org/cookbook/composing-stores.html#Shared-Actions

We recently discovered that we had cross-request pollution caused by bad use of Pinia stores after an await in Nuxt. With a large codebase, it was tough to track down every instance of the issue. So, we ended up creating a custom version of defineStore, similar to the one in this PR, to help prevent developers from misusing Pinia stores.

I think it would be useful if this could be implemented directly in the Pinia Nuxt module.

Description of the changes

The Nuxt Pinia module does the following for each request:

// Create the instance for this request
const pinia = createPinia()
// Store the instance within the Nuxt context (mainly for serialization after)
nuxtApp.vueApp.use(pinia)
// Set the Pinia global instance (that will be used by Pinia and is subject to cross-request pollution)
setActivePinia(pinia)

The module also provides a helper usePinia() that retrieves the Pinia instance from the Nuxt context and not from the Pinia global variable. Conveniently, this helper will throw an error if used after an await (outside of a Nuxt-aware context)!

This PR ensures that on the server we always use usePinia() to get the current Pinia instance. By doing so useStore will systematically throw if used after an await on the server (similarly to the useNuxtApp composable)

Breaking change

Server-side, using a store outside a Nuxt-aware context will now result in an error. Previously, it would continue without error, but sometimes, the wrong Pinia instance was used.

Error in dev:

[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function

Error in prod:

[nuxt] instance unavailable

Copy link

netlify bot commented Dec 11, 2024

Deploy Preview for pinia-playground canceled.

Name Link
🔨 Latest commit d357d58
🔍 Latest deploy log https://app.netlify.com/sites/pinia-playground/deploys/675c6897e2bd7f0008402bae

Copy link

netlify bot commented Dec 11, 2024

Deploy Preview for pinia-official canceled.

Name Link
🔨 Latest commit d357d58
🔍 Latest deploy log https://app.netlify.com/sites/pinia-official/deploys/675c6897508ac30008a40b2a

Copy link
Member

@posva posva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Do you have a reproduction of the problem where correctly using the useStore() composable returned by defineStore() doesn't work?

@pguilbert
Copy link
Author

Thanks for the PR! Do you have a reproduction of the problem where correctly using the useStore() composable returned by defineStore() doesn't work?

"correctly" no. The purpose of this PR is to ease the detection of incorrect usage. In the current version bad usage of a store after a await will work without warning in low-traffic environments. But, in cases of simultaneous requests, there is a possibility that pinia uses the pinia instance from another request.

I can provide a reproduction if needed, but this is already a documented case:

Make sure all of your useStore() calls appear before any await. Otherwise, this could lead to using the wrong Pinia instance in SSR apps: pinia.vuejs.org/cookbook/composing-stores.html#Shared-Actions

For context: We recently found out that we had cross-request pollution caused by bad use of Pinia in Nuxt. With our large codebase, it was tough to track down every instance of the issue. So, we ended up creating a custom version of defineStore, similar to this one, to help prevent developers from misusing Pinia stores.

(I'll update the PR description with those infos.)

@pguilbert pguilbert changed the title fix: throw when a store is used outside of a Nuxt-aware context. (POC) fix: throw when a store is used outside of a Nuxt-aware context. Dec 11, 2024
@posva
Copy link
Member

posva commented Dec 12, 2024

Thanks for the great description of the problem! I agree it's a nice addition

Copy link

pkg-pr-new bot commented Dec 12, 2024

Open in Stackblitz

npm i https://pkg.pr.new/pinia@2857
npm i https://pkg.pr.new/@pinia/nuxt@2857
npm i https://pkg.pr.new/@pinia/testing@2857

commit: 2a0c9ed

Copy link

codecov bot commented Dec 12, 2024

Codecov Report

Attention: Patch coverage is 0% with 13 lines in your changes missing coverage. Please review.

Project coverage is 87.96%. Comparing base (2071db2) to head (2a0c9ed).

Files with missing lines Patch % Lines
packages/nuxt/src/runtime/composables.ts 0.00% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##               v2    #2857      +/-   ##
==========================================
- Coverage   88.75%   87.96%   -0.79%     
==========================================
  Files          19       19              
  Lines        1449     1462      +13     
  Branches      226      226              
==========================================
  Hits         1286     1286              
- Misses        162      175      +13     
  Partials        1        1              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

packages/nuxt/test/nuxt.spec.ts Outdated Show resolved Hide resolved
packages/nuxt/src/runtime/composables.ts Outdated Show resolved Hide resolved
packages/nuxt/src/runtime/composables.ts Outdated Show resolved Hide resolved
@pguilbert
Copy link
Author

Thanks for the great description of the problem! I agree it's a nice addition

Nice! I'll complete the typing and refine the test to move this PR out of draft.

Prefer the Nuxt Pinia instance over the global active Pinia instance. Since the Nuxt Pinia instance is discarded after each request, it ensures that we can't accidentally use one from another request. Additionally, `usePinia` will throw an error when used outside of a Nuxt-aware context.

The error is as follows in dev :
> [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.
@pguilbert pguilbert force-pushed the error-usage-after-await branch from 2a0c9ed to d357d58 Compare December 13, 2024 17:02
@pguilbert pguilbert marked this pull request as ready for review December 16, 2024 13:47
@pguilbert pguilbert requested a review from posva December 17, 2024 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants