storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.81k stars 9.34k forks source link

[Bug]: Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. #24347

Closed doutatsu closed 1 year ago

doutatsu commented 1 year ago

Describe the bug

When I go directly to a story that uses a pinia store, it fails with the following error:

https://github.com/storybookjs/storybook/assets/4270980/4580bb7e-db36-44fe-8480-bf016d4a6db5

image

Interestingly if I first visit a story without a store and then navigate to a story with one, the error does not occur

To Reproduce

Reproduction link

  1. Start storybook
  2. Go directly to Pricing story and reload the page -> you will see this error
  3. Go to the any EXAMPLE stories and then back to Pricing -> it's fixed
  4. Go back to Pricing -> reload page -> issue is back

System

Environment Info:
  System:
    OS: macOS 12.5
    CPU: (8) arm64 Apple M2
  Binaries:
    Node: 20.7.0 - ~/.nvm/versions/node/v20.7.0/bin/node
    Yarn: 1.22.19 - ~/.yarn/bin/yarn
    npm: 10.1.0 - ~/.nvm/versions/node/v20.7.0/bin/npm
  Browsers:
    Chrome: 116.0.5845.179
    Edge: 117.0.2045.47
    Safari: 15.6
  npmPackages:
    @storybook/addon-actions: 7.4.5 => 7.4.5
    @storybook/addon-essentials: 7.4.5 => 7.4.5
    @storybook/addon-interactions: 7.4.5 => 7.4.5
    @storybook/addon-links: 7.4.5 => 7.4.5
    @storybook/testing-library: 0.2.1 => 0.2.1
    @storybook/vue3: 7.4.5 => 7.4.5
    @storybook/vue3-vite: 7.4.5 => 7.4.5

Additional context

I've had this error since originally trying to upgrade to 7.0 and it persisted all the way till 7.4.5 and I am still unable to find why this happens.

I run the latest Vite with Vue 3 and vanilla JS, not TS in my setup.

Here is my package.json:

{
  "name": "Kenmei",
  "version": "1.0.0",
  "private": true,
  "engines": {
    "node": "20.7",
    "pnpm": "8.7.6"
  },
  "packageManager": "pnpm@8.7.6",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "test": "TZ=UTC vitest",
    "test:verbose": "TZ=UTC vitest --reporter verbose",
    "lint": "eslint --ext .js,.vue . --ignore-path .gitignore",
    "lint:fix": "eslint --ext .js,.vue . --ignore-path .gitignore --fix",
    "storybook:build": "storybook build",
    "storybook:serve": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "coveralls": "cat ./coverage/lcov.info | coveralls",
    "chromatic": "npx chromatic"
  },
  "dependencies": {
    "@chenfengyuan/vue-number-input": "2.0.1",
    "@formkit/auto-animate": "0.8.0",
    "@headlessui/vue": "1.7.16",
    "@heroicons/vue": "1.0.6",
    "@intlify/unplugin-vue-i18n": "1.2.0",
    "@popperjs/core": "2.11.8",
    "@sentry/vue": "7.68.0",
    "@splidejs/vue-splide": "git+https://github.com/doutatsu/vue-splide.git#master",
    "@tailwindcss/aspect-ratio": "0.4.2",
    "@tailwindcss/forms": "0.5.3",
    "@tailwindcss/line-clamp": "0.4.2",
    "@tailwindcss/typography": "0.5.9",
    "@unhead/vue": "1.7.4",
    "@vitest/coverage-istanbul": "0.34.5",
    "@vuelidate/core": "2.0.3",
    "@vuelidate/validators": "2.0.4",
    "@vueuse/components": "10.4.1",
    "@vueuse/core": "10.4.1",
    "@vueuse/integrations": "10.4.1",
    "axios": "0.27.2",
    "axios-auth-refresh": "3.3.6",
    "body-scroll-lock": "4.0.0-beta.0",
    "change-case": "4.1.2",
    "chromatic": "7.1.0",
    "dayjs": "1.11.10",
    "fast-equals": "5.0.1",
    "filepond": "4.30.4",
    "filepond-plugin-file-encode": "2.1.11",
    "filepond-plugin-file-validate-type": "1.2.8",
    "he": "1.2.0",
    "marked": "9.0.3",
    "mixpanel-browser": "2.46.0",
    "nanoid": "4.0.2",
    "overlayscrollbars": "2.3.0",
    "overlayscrollbars-vue": "0.5.5",
    "pinia": "2.1.6",
    "public-ip": "6.0.1",
    "qs": "6.11.2",
    "resize-observer-polyfill": "1.5.1",
    "suncalc": "1.9.0",
    "v-click-outside": "3.1.2",
    "v-wave": "1.5.1",
    "vue": "3.3.4",
    "vue-advanced-cropper": "2.8.8",
    "vue-filepond": "7.0.4",
    "vue-final-modal": "4.4.5",
    "vue-i18n": "9.4.1",
    "vue-loading-overlay": "6.0.3",
    "vue-my-toasts": "2.0.1",
    "vue-router": "4.2.5",
    "vue-screen": "2.3.2",
    "vue-scrollto": "2.20.0",
    "vue-select": "4.0.0-beta.1",
    "vue-tippy": "6.3.1",
    "vue3-slide-up-down": "2.0.1",
    "vue3-touch-events": "4.1.4"
  },
  "devDependencies": {
    "@pinia/testing": "0.1.3",
    "@storybook/addon-actions": "7.4.5",
    "@storybook/addon-essentials": "7.4.5",
    "@storybook/addon-interactions": "7.4.5",
    "@storybook/addon-links": "7.4.5",
    "@storybook/testing-library": "0.2.1",
    "@storybook/vue3": "7.4.5",
    "@storybook/vue3-vite": "7.4.5",
    "@testing-library/jest-dom": "5.17.0",
    "@unhead/addons": "1.7.4",
    "@vitejs/plugin-vue": "4.3.4",
    "@volar/vue-language-plugin-pug": "1.6.5",
    "@vue/compiler-sfc": "3.3.4",
    "@vue/language-plugin-pug": "1.8.13",
    "@vue/test-utils": "2.4.1",
    "ajv": "8.12.0",
    "autoprefixer": "10.4.16",
    "axios-mock-adapter": "1.22.0",
    "coveralls": "3.1.1",
    "eslint": "8.49.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-plugin-import": "2.28.1",
    "eslint-plugin-vue": "9.17.0",
    "eslint-plugin-storybook": "0.6.13",
    "fishery": "2.2.2",
    "jest-serializer-vue": "3.1.0",
    "jsdom": "22.1.0",
    "postcss": "8.4.30",
    "pug": "3.0.2",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "sass": "1.66.1",
    "storybook": "7.4.5",
    "tailwindcss": "3.2.4",
    "vite": "4.4.9",
    "vite-plugin-pwa": "0.16.5",
    "vite-plugin-webfont-dl": "3.7.6",
    "vitest": "0.34.5"
  }
}

Here is my complete preview.js where I specify pinia and everything else:

import { setup } from '@storybook/vue3';
import { createHead } from '@unhead/vue';
import { createPinia } from 'pinia';
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
import { createVfm } from 'vue-final-modal';
import VueScreen from 'vue-screen';
import VueTippy from 'vue-tippy';
import VueScrollTo from 'vue-scrollto';
import vClickOutside from 'v-click-outside';
import Vue3TouchEvents from 'vue3-touch-events';
import VueMyToasts from 'vue-my-toasts';
import VWave from 'v-wave';

import '@/stylesheets/global.scss';
import '@/stylesheets/transitions.scss';
import 'vue-final-modal/style.css';

const pinia = createPinia();
const head = createHead();
const vfm = createVfm();

import router from '@/router/index';

import BaseNotification from '@/components/base_components/BaseNotification.vue';

// Plugins
import HeroIcons from '@/plugins/heroicons';
import globalComponents from '@/plugins/globalComponents';
import darkTheme from '@/plugins/darkTheme';
import { i18n } from '@/plugins/i18n';

setup((app) => {
  app.directive('clickOutside', vClickOutside);

  app.use(pinia);
  app.use(HeroIcons);
  app.use(autoAnimatePlugin);
  app.use(globalComponents);
  app.use(darkTheme);
  app.use(i18n);
  app.use(head);
  app.use(vfm);
  app.use(VueScrollTo);
  app.use(VueScreen);
  app.use(VWave);
  app.use(Vue3TouchEvents, { disableClick: true, touchHoldTolerance: 300, });
  app.use(VueMyToasts, {
    component: BaseNotification,
    options: {
      position: 'top-right',
    },
  });
  app.use(VueTippy, {
    defaultProps: {
      animation: 'shift-away',
      theme: 'light',
      arrow: false,
      touch: false,
    },
  });

  // Setup router with toasts
  const routerInstance = router(app.config.globalProperties.$toasts);

  app.use(routerInstance);
});

export const decorators = [(story, { parameters }) => {
    const theme = parameters.theme || 'light';
    const themeToAdd = theme === 'light' ? 'light' : 'dark';
    const themeToRemove = theme === 'light' ? 'dark' : 'light';
    const htmlElement = document.querySelector('html');

    htmlElement.classList.add(themeToAdd);
    htmlElement.classList.remove(themeToRemove);

    return story();
}]

export const parameters = {
  layout: 'fullscreen',
  actions: { argTypesRegex: "^on[A-Z].*" },
  chromatic: {
    pauseAnimationAtEnd: true,
    viewports: [375, 1280],
  },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
doutatsu commented 1 year ago

It almost feels like setup doesn't run before the story loads or something like that. Here is an example of a story that fails:

import { useUserStore } from '@/store/user';

import PricingView from '@/views/Pricing.vue';

export default {
  title: 'Views/Pricing',
};

const Template = (args) => ({
  components: { PricingView },
  setup() { return { args }; },
  template: '<pricing-view v-bind="args" />',
});

export const Pricing = Template.bind({});
export const DarkTheme = Template.bind({});

DarkTheme.parameters = { theme: 'dark' };
Pricing.loaders = [
  async () => {
    const userStore = useUserStore();

    userStore.currentUser = {};
  },
];

DarkTheme.parameters = { theme: 'dark' };
DarkTheme.loaders = [
  async () => {
    const userStore = useUserStore();

    userStore.currentUser = {};
  },
];
doutatsu commented 1 year ago

Any thoughts on this @chakAs3?

doutatsu commented 1 year ago

Or @Integrayshaun as you closed the other issue 😫

vanessayuenn commented 1 year ago

Hi, thanks for reporting this. do you have a reproduction repo you can share? If not, can you create one (see how to create a repro). We prioritize bug reports that have reproduction. Thank you! 🙏

chakAs3 commented 1 year ago

Hi @doutatsu i have just created a repo with same package.json provided , but using only pinia to narrow the issue,

https://stackblitz.com/~/github.com/chakAs3/my-vue-app/blob/master/.storybook/preview.js

i have include your story named Pinia, please use this repo as base for your repro, i'm not able to reproduce the issue

doutatsu commented 1 year ago

Thanks @chakAs3 - I was actually just about started to working on reproduction myself. Funny enough, your repro doesn't render the story even on load 😬

image
doutatsu commented 1 year ago

@chakAs3 Yup, your reproduction actually has the exact problem I've described. Here is the simple steps to get the error in your repro:

  1. Start storybook
  2. Go directly to Pricing story and reload the page -> you will see this error
  3. Go to the any EXAMPLE stories and then back to Pricing -> it's fixed
  4. Go back to Pricing -> reload page -> issue is back
doutatsu commented 1 year ago

Here's a video as well:

https://github.com/storybookjs/storybook/assets/4270980/4de72a26-ec2f-4553-8211-160723a6f6ee

doutatsu commented 1 year ago

@vanessayuenn I've updated description with the reproduction link made by @chakAs3 as well as steps to reproduce this issue

chakAs3 commented 1 year ago

@doutatsu i tried again on my local, i pulled the same repo, and it did work, Stackblitz could have some issue

https://github.com/storybookjs/storybook/assets/711292/75c0e168-d178-4c5b-a44b-a107cd6c6f21

I'm sorry if i'm not able to check your issue in time, i'm very busy this week for my private business at GITEX Technology Week in Dubai, so bear with me. i ll put more time next week

doutatsu commented 1 year ago

No worries @chakAs3, I've been trying to upgrade for months, so at this point, I can wait as long as needed 😂

Also, just in case - did you follow the reproduction steps I described? As your video doesn't seem to follow the steps, there is no error present, which is expected.

You need to specifically go to the Pricing story first and then reload the page to force the error. After that, if you switch to other stories and back, it would fix itself. But crucially, the error appears when you reload the page (or in the case of Chromatic, on load)

chakAs3 commented 1 year ago

You need to specifically go to the Pricing story first and then reload the page to force the error. After that, if you switch to other stories and back, it would fix itself. But crucially, the error appears when you reload the page

oh Sorry i did not read the repro steps, i thought first load ok i will check it tonight 👍

chakAs3 commented 1 year ago

@doutatsu I've just confirmed that using the loader should be independent of the rendering framework, be it Vue or React. The loader can't utilize a Vue plugin since it's currently unavailable. Loaders are primarily for fetching data via HTTP requests. Pinia, on the other hand, is a state manager that can be used as usual inside the setup function.

If I'm not mistaken, the reason for considering Pinia within the loader is because you're handling data loading within the store.

The good news is that you don't need to worry about this when working with Vue. The issue Vue developers face in Storybook is that Storybook was originally built with a React mindset, which differs from Vue, Svelte, or Solid. To address this, I've created a separate group where I can leverage the Vue mindset and provide more documentation, examples, and content.

Unfortunately, even though I'd like to, I can't clone myself. In the past three days, I've had only six hours of sleep. This is because GITEX week in Dubai is a huge event that occurs once a year.

doutatsu commented 1 year ago

Ah, that's super helpful @chakAs3 - I can confirm moving store definition into setup step fixes the issue, but I am now unsure on how to properly manage store state between stories. I used loader to setup data in the store, but I don't see a way to do it when store is set in setup.

No rush on replying, just going to leave this comment for you when you got free time again and I'll continue re-reading the docs to figure out if there is a way

Here an example of a story I am trying to set data for:

import { computed } from 'vue';
import { factories } from '@/../tests/factories';

import { useUserStore } from '@/store/user';
import BaseNavigation from '@/components/base_components/BaseNavigation.vue';

export default {
  title: 'Components/Base Components/Navigation',
};

const Template = (args) => ({
  components: { BaseNavigation },
  setup() {
    const store = useUserStore();

    return { args, store };
  },
  template: `
    <div class="h-screen flex flex-col">
      <header class='flex-1 bg-gray-50 dark_bg-slate-700'>
        <base-navigation v-bind="args" />
      </header>
    </div>
  `,
});

export const SignedIn = Template.bind({});
SignedIn.loaders = [
  async (comp) => {
    // const userStore = useUserStore();
    // userStore.currentUser = factories.user.build();
    // userStore.darkMode = false;

    comp.store.currentUser = factories.user.build();
    comp.store.darkMode = false;
  },
];

export const NotSignedIn = Template.bind({});
NotSignedIn.loaders = [
  async (comp) => {
    // const userStore = useUserStore();
    // userStore.currentUser = {};
    // userStore.darkMode = false;

    comp.args.store.currentUser = {};
    comp.args.store.darkMode = false;
  },
];

export const DarkTheme = Template.bind({});

DarkTheme.parameters = { theme: 'dark' };
DarkTheme.loaders = [
  async (comp) => {
    // const userStore = useUserStore();
    // userStore.currentUser = {};
    // userStore.darkMode = true;

    comp.args.store.currentUser = {};
    comp.args.store.darkMode = false;
  },
];
chakAs3 commented 1 year ago

I'm glad to be helpful, actually i wanted to suggest CSF3 format which comes with v7 and has more features and future 😆 , check the same repo i have push some change, may help you to switch. Good luck

doutatsu commented 1 year ago

I forgot to come back and close this myself - I have fixed it by moving everything into setup blocks as you described @chakAs3 - it's not as DRY as it was before, but I managed to upgrade, so I am happy 👍🏻 Thanks for the help

chakAs3 commented 1 year ago

I forgot to come back and close this myself - I have fixed it by moving everything into setup blocks as you described @chakAs3 - it's not as DRY as it was before, but I managed to upgrade, so I am happy 👍🏻 Thanks for the help

Welcome anytime @doutatsu feel free to get in touch if you face any issue.

stephiescastle commented 5 months ago

I have fixed it by moving everything into setup blocks as you described

@doutatsu do you have an example of this? I'm facing the same issue right now.

doutatsu commented 5 months ago

@stephiescastle Here's a simple story example. You can then setup your store data any way you want as well in that setup block:

import { useUserStore } from '@/store/user';
import { factories } from '@/../tests/factories';

import PricingView from '@/views/Pricing.vue';

export default {
  title: 'Views/Pricing',
};

const Default = {
  render: (args) => ({
    components: { PricingView },
    setup() {
      const store = useUserStore();

      store.currentUser = {};

      return { args };
    },
    template: '<pricing-view v-bind="args" />',
  }),
};

export const Pricing = { ...Default };
export const Subscribed = {
  render: (args) => ({
    components: { PricingView },
    setup() {
      const store = useUserStore();

      store.currentUser = factories.user.withPremium().build();

      return { args };
    },
    template: '<pricing-view v-bind="args" />',
  }),
};
export const DarkTheme = { ...Default, parameters: { theme: 'dark' } };