vuejs / pinia

🍍 Intuitive, type safe, light and flexible Store for Vue using the composition api with DevTools support
https://pinia.vuejs.org
MIT License
12.95k stars 1.03k forks source link

Possible edge case in devtools integration: Cannot read properties of null (reading '__VUE_DEVTOOLS_APP_RECORD_ID__') #1630

Open basuneko opened 2 years ago

basuneko commented 2 years ago

Reproduction

https://github.com/basuneko/pinia-devtools-issue

Steps to reproduce the bug

  1. Checkout the reproduction repo and run npm run serve
  2. Open a new tab and devtools
  3. Open the reproduction app - you should see the default vue cli homepage
  4. Wait ~60 seconds for the devtools timeout

Expected behavior

Actual behavior

✅ Both 'bacon' and 'yolo' stores are indeed listed in the pinia devtools tab

❌ However, instead of the 🍍 messages, after a timeout, devtools spits out a bunch of errors

Screen Shot 2022-09-06 at 5 31 46 PM
* Error: Timed out getting app record for app at backend.js:1160:14
* [Hook] Error in async event handler for devtools-plugin:setup with args:
* TypeError: Cannot read properties of null (reading '__VUE_DEVTOOLS_APP_RECORD_ID__') at getAppRecordId (backend.js:1103:11)

Additional information

Hi. I'm migrating a Vue 2, Vuex 3, vuex-module-decorators project to Pinia. For some historic reasons, our store module files export the actual store instance and, while that seems to work with Pinia as well, it seems to break something in the devtools integration. Here's an example:

// @/store/pinia.ts

import { Vue } from 'vue'
import { PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin) // it doesn't matter whether it's called here or in main.ts

export const pinia = createPinia()
// @/store/useUserStore.ts
import { defineStore } from 'pinia'
import { pinia } from '@/store/pinia'

const useUserStore = defineStore('user', { ... })

export const userStore = useUserStore(pinia) 

I'm following the Stores outside of components guide, and passing an instance of Pinia into useUserStore. Functionality-wise, this seems to work fine. I have plenty of cypress tests and they're all passing after the migration. But the devtools errors are mildly concerning.

Vue 2.7.5 -> 2.7.10 Pinia 2.0.21 Devtools extension 6.2.1 Chrome 105.0.5195.52

posva commented 2 years ago

I cannot reproduce but the error comes from the devtools anyway. Make sure you have the latest devtools version installed and not the beta. I managed to reproduce it.

As a side note, I discourage you from doing this:

const useUserStore = defineStore('user', { ... })

export const userStore = useUserStore(pinia) 

Instead, use the patterns shown in docs with setup() or with mapStores() (& co). It's important the useStore(pinia) are called after new Vue(). If anybody wants to give this a shot, go ahead

DallasHoff commented 1 year ago

+1, I encountered this when trying to use a Pinia store in a Vue Router navigation guard in a Vue 2.7 app, importing the Pinia instance from main.ts.

jorismak commented 1 year ago

I'm a bit confused by this.

But how do we set the state on the store to some initial stuff (like from an API) before 'loading the app' (doing the very first new Vue()).

In my code I have this:

Vue.use(PiniaVuePlugin);
const pinia = createPinia();

const mainStore = useMainStore(pinia);

mainStore.refreshUser().finally(() => {
    new Vue({
        router,
        vuetify,
        pinia,
        render: (h) => h(App),
    }).$mount("#app");
});

To make an axios call to see if we are logged in or not, and store that in the store, and only then load up the very first Vue component. This way the Vue app knows from the very first moment if it has a user or not and to go to a login-page or not.

I used to do this method with vuex without issues. Is it just the devtools that are having issue with this or do I have a problem in my code I didn't encounter yet?

lee1nna commented 1 year ago

I'm having the same problem, is there a solution? 😥

jorismak commented 1 year ago

I still had cases of using it outside a component without giving the instance from my main.ts. like in a router hook.

Carefully looking it all through and fixing it , seemed to fix it. But now after a while it's back.

BoxenOfDonuts commented 1 year ago

@basuneko I wound up here because I was having a similar issue, turns out I had a useXyzStore() running before Pinia was installed, a similar example here

I refactored your example a bit to get rid of the errors, it works exactly the same way but I got rid of these: export const userStore = useUserStore(pinia)

and just did a normal import of the store:

// in HelloWorld.vue
import { useBaconStore } from '@/useBaconStore';

You can see a diff here

c-malecki commented 1 year ago

@jorismak

I'm a bit confused by this.

But how do we set the state on the store to some initial stuff (like from an API) before 'loading the app' (doing the very first new Vue()).

In my code I have this:

Vue.use(PiniaVuePlugin);
const pinia = createPinia();

const mainStore = useMainStore(pinia);

mainStore.refreshUser().finally(() => {
    new Vue({
        router,
        vuetify,
        pinia,
        render: (h) => h(App),
    }).$mount("#app");
});

To make an axios call to see if we are logged in or not, and store that in the store, and only then load up the very first Vue component. This way the Vue app knows from the very first moment if it has a user or not and to go to a login-page or not.

I used to do this method with vuex without issues. Is it just the devtools that are having issue with this or do I have a problem in my code I didn't encounter yet?

Sorry for digging this up awhile after you posted, but did you ever find a solution for this? This is the exact same situation I'm facing currently.

jorismak commented 1 year ago

Well, the code is working fine. For me it's in a SPA , so it's client side only. The weird errors in the devtools don't seem to hinder the app or the devtools... It just hints at a pattern that's discouraged , specially for SSR leaking . Since this is for me without SSR , i have no issue with it.

I just don't see a better pattern to so this.

Thinking out loud now: i want the store to have a valid 'logged in or not' state before the first route hits. If you do it later, you get a flash of the page you are visiting, and then the route guard kicking in and getting redirected to the login route after seeing a flash of something else (or some sort of loading indicator ). I didn't want that , so i refresh my logged in state before doing the first new Vue(). Now, isn't it an option to do this refreshUser call in a onCreated or another lifecycle hook of the very first root Vue object ? In that case Vue would've been 'started' and pinia would have a valid unit i guess. You still have to be careful to always give the pinia context to any 'useStore' you use outside of a script-setup.

The thing is that my refreshUser call is async, it returns a promise . I basically want that promise to resolve (or error ) before attempting to create and mount the first route. I don't know if this is possible .

Another completely different method is to embed that initial state data in the initial server request that returns the html to start your app . There could be a script tag with some json inside it, and pinia can maybe fetch that json data to setup initial data in a store. 'hydration'. If pinia has no automated way for this , your code in your store can do it manually as a last resort. (Use document.getElementById to find a script tag with an id, use inner text/html to get the json inside. And set your default store values depending on this object, if it all works out.

But this means that your backend must generate the html dynamically . And normally i just have a static index.html.

In a SSR situation - which means nuxt3 for me - i just use the pinia nuxt module. The refreshUser call is there in a nuxt middleware, which bypasses this whole problem. (do i have a valid user in the store ? Do nothing . If not, check if we called refreshUser at least once. If we did, that means we redirect to login. If we now have a valid user after calling refreshUser , continue like normal).

I basically use 'user == undefined' for 'store is not initialized yet, 'user == null' for no user logged in, and otherwise we have a valid user .

sumerokr commented 2 months ago

I am facing the same issue with Nuxt2.

I have just one store that I try to use from the Nuxt plugin.

// /plugins/my-plugin.js
import { useMyStore } from "@/stores/myStore";

export default function ({ app, store, $pinia }) {
  console.log({ app, store, $pinia });

  const myStore = useMyStore($pinia);
}

Leads to console errors

[Hook] Error in async event handler for devtools-plugin:setup with args:
TypeError: Cannot read properties of null (reading '__VUE_DEVTOOLS_APP_RECORD_ID__')
[Bridge] Error in listener for event b:devtools-plugin:list with args
// package.json
"dependencies": {
  "@nuxtjs/composition-api": "^0.34.0",
  "@pinia/nuxt": "^0.2.1",
  "core-js": "^3.25.3",
  "nuxt": "^2.15.8",
  "pinia": "^2.1.7",
  "vue": "^2.7.10",
  "vue-server-renderer": "^2.7.10",
  "vue-template-compiler": "^2.7.10"
},
// nuxt.config.js
plugins: ["~/plugins/my-plugin.js"],
buildModules: [
  "@nuxtjs/composition-api/module",
  ["@pinia/nuxt", { disableVuex: false }],
],