pinia icon indicating copy to clipboard operation
pinia copied to clipboard

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

Open basuneko opened this issue 2 years ago • 9 comments

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

  • 'bacon' and 'yolo' stores are listed in the pinia devtools tab and can be inspected
  • the console shows pinia initialisation messages
      🍍 bacon store added
      🍍 yolo store added
    
  • No errors are raised in the console;

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

basuneko avatar Sep 06 '22 05:09 basuneko

~~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

posva avatar Sep 06 '22 09:09 posva

+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.

DallasHoff avatar Nov 17 '22 18:11 DallasHoff

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?

jorismak avatar Dec 09 '22 10:12 jorismak

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

lee1nna avatar Mar 15 '23 00:03 lee1nna

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.

jorismak avatar Mar 15 '23 07:03 jorismak

@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

BoxenOfDonuts avatar Apr 14 '23 18:04 BoxenOfDonuts

@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.

c-malecki avatar Apr 25 '23 12:04 c-malecki

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 .

jorismak avatar Apr 25 '23 18:04 jorismak

It seems that it can be executed in nextTick?

yaytgif avatar Aug 10 '23 03:08 yaytgif

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 }],
],

sumerokr avatar Jun 24 '24 07:06 sumerokr