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

[Bug]: Components lose reactivity when using Decorators (7.0.0-RC9 Vue3) #21812

Closed
JorisAerts opened this issue Mar 29, 2023 · 10 comments
Closed
Assignees

Comments

@JorisAerts
Copy link

JorisAerts commented Mar 29, 2023

Describe the bug

After upgrading from 6.5 to 7 RC9, the component in the story doesn't adapt anymore when I change the controls in the Controls tab. The reactivity is just gone. I'm using @storybook/vue3 & @storybook/vue3-vite.

After some investigation, I found some workarounds, but this seems like a bug.

To Reproduce

Scenario 1 (using a global decorator)

The following code results in components losing reactivity, when changing props from within the Controls panel:

/**
 * The vuetify story wrapper
 */
const VuetifyStoryWrapper = defineComponent({
  name: 'storybook-vuetify-story-wrapper',
  props: { theme: { type: String as PropType<ThemeName>, default: DEFAULT_THEME } },
  setup(props, { attrs, slots }) {
    return () => h(VApp, { theme: props.theme }, () => h(VMain, slots.default))
  },
})

/**
 * The decorator that wraps stories within an v-app and v-main tag (required by Vuetify)
 */
export const vuetifyDecorator: Decorator = (storyFn: any, context: any) => {
  const theme = context.globals.themeSwitcher || DEFAULT_THEME
  const story = storyFn()
  return {
    name: 'storybook-vuetify',
    render: () => h(VuetifyStoryWrapper, { theme }, () => h(story, context.args)),
  }
}

🛠️ WORKAROUND: By just removing the parameters-object from the top-level component in the render-function, reactivity is enabled back again:

render: () => h(VuetifyStoryWrapper, () => h(story, context.args)),

This is kind of annoying, because it forces me to use a "global variable" as a Vue-ref to which I can write the current theme, and have it picked up by the vuetifyDecorator. 😕

Scenario 2 (using a story-decorator)

When using a decorator on a story, like below, the decorator makes the control to lose reactivity:

export default {
  title: 'Design System/Atoms/Button',
  component: XBtn,
  decorators: [WithPadding],
  render: (args) => ({
    setup() { return { args } },
    components: { XBtn },
    template: '<XBtn v-bind="args">{{args.content}}</XBtn>',
  }),
} as Meta<typeof XBtn>

The decorator looks like this:

export const WithPadding = () => ({
  name: 'storybook-decorator-with-padding',
  template: `
    <div class="pa-8 py-6">
      <story />
    </div>
  `,
})

🛠️ WORKAROUND: Pass story as an argument and render the component using h makes reactivity come back:

import { h } from 'vue'

export const WithPadding = (story: any) => ({
  name: 'storybook-decorator-with-padding',
  render: () => h('div', { class: 'pa-8 py-6' }, h(story())),
})

(I had to use any because I don't know the type of story).

System

Environment Info:

  System:
    OS: macOS 13.2.1
    CPU: (10) arm64 Apple M1 Pro
  Binaries:
    Node: 18.12.1 - ~/Development/Library/node.js/18.12.1/node-v18.12.1-darwin-arm64/bin/node
    Yarn: 3.5.0 - ~/Development/Library/node.js/18.12.1/node-v18.12.1-darwin-arm64/bin/yarn
    npm: 8.19.2 - ~/Development/Library/node.js/18.12.1/node-v18.12.1-darwin-arm64/bin/npm
  Browsers:
    Chrome: 111.0.5563.110
    Firefox: 111.0.1
    Safari: 16.3

Additional context

For global decorators, when I add a console.log statement in the decorator's render-function, it's only logged once, but not after I update the control.

render: () => h(VuetifyStoryWrapper, { theme }, () => {
  console.log("hello")
  return h(story, context.args)
})

Without the { theme } props object, the console.log is triggered upon every change and works as expected:

render: () => h(VuetifyStoryWrapper, () => {
  console.log("hello")
  return h(story, context.args)
})

Adding a console.log in the render-function within the story itself, DOES log the changes, but does not affect the rendered component itself. The args DO seem to change, but are not applied:

setup() {
  console.log({ ...args })
  return { args }
},
@JorisAerts JorisAerts changed the title [Bug]: Components loose reactivity when using Decorators (7.0.0-RC9 Vue) [Bug]: Components loose reactivity when using Decorators (7.0.0-RC9 Vue3) Mar 29, 2023
@JorisAerts JorisAerts changed the title [Bug]: Components loose reactivity when using Decorators (7.0.0-RC9 Vue3) [Bug]: Components lose reactivity when using Decorators (7.0.0-RC9 Vue3) Mar 29, 2023
@shilman shilman added vue3 and removed needs triage labels Mar 29, 2023
@shilman
Copy link
Member

shilman commented Mar 29, 2023

I believe this is being addressed in #21273

@chakAs3 @kasperpeulen can you please confirm?

@kasperpeulen
Copy link
Contributor

@shilman Yes, this would be solved by that PR (assuming we can fix all cases in the PR, but that is at least the goal).

@Jman
Copy link

Jman commented Mar 29, 2023

I tried the recipe and ran into the same issue. I replaced the render function with a component and story controls started working fine:

import type { Decorator } from '@storybook/vue3';
import StoryWrapper from './StoryWrapper.vue'; //https://github.com/Integrayshaun/vue3-vuetify-storybook-recipe-example/blob/main/.storybook/StoryWrapper.vue

export const DEFAULT_THEME = 'light';

export const withVuetifyTheme: Decorator = (story, context) => {
  const themeName = context.globals.theme || DEFAULT_THEME;

  return {
    components: { StoryWrapper, story },
    setup() {
      return {
        themeName,
      }
    },
    template: `
      <story-wrapper :theme-name="themeName">
        <template #story>
          <story />
        </template>
      </story-wrapper>
    `
  }
};

But the story for HelloWorld lose its reactivity for theme switching.

@chakAs3
Copy link
Contributor

chakAs3 commented Mar 29, 2023

I tried the recipe and ran into the same issue. I replaced the render function with a component and story controls started working fine:

import type { Decorator } from '@storybook/vue3';
import StoryWrapper from './StoryWrapper.vue'; //https://github.com/Integrayshaun/vue3-vuetify-storybook-recipe-example/blob/main/.storybook/StoryWrapper.vue

export const DEFAULT_THEME = 'light';

export const withVuetifyTheme: Decorator = (story, context) => {
  const themeName = context.globals.theme || DEFAULT_THEME;

  return {
    components: { StoryWrapper, story },
    setup() {
      return {
        themeName,
      }
    },
    template: `
      <story-wrapper :theme-name="themeName">
        <template #story>
          <story />
        </template>
      </story-wrapper>
    `
  }
};

But the story for HelloWorld lose its reactivity for theme switching.

Hi @Jman we are aware of this issue there is already a PR that fixes this #21273

@chakAs3
Copy link
Contributor

chakAs3 commented Mar 29, 2023

I believe this is being addressed in #21273

@chakAs3 @kasperpeulen can you please confirm?

Yes @shilman it covers all cases including global decorators, that use function or template,i will added this case to e2e tests and merge next. to have it ready and avoid any merge conflict

@chakAs3
Copy link
Contributor

chakAs3 commented Apr 1, 2023

hi @Jman @JorisAerts , you can check out this repo i have created some stories using Vuetify, i think it is a minimal way to implement latest Vuetify, pretty simple similar to what was in receipt with some minors changes.
vue3-vuetify-examples.

There is also other stories with different type of decorators (global, story decorator, function decorator , and template decorator). CSF2 format is there as well, let me know if that seems ok for you

For global decorators, when I add a console.log statement in the decorator's render-function, it's only logged once, but not after I update the control.

render: () => h(VuetifyStoryWrapper, { theme }, () => {
  console.log("hello")
  return h(story, context.args)
})

Without the { theme } props object, the console.log is triggered upon every change and works as expected:

render: () => h(VuetifyStoryWrapper, () => {
  console.log("hello")
  return h(story, context.args)
})

i'm really impressed by the quality of your bug report as you really pointed all the issues even you could detect when it works as expected by removing { theme } actually in my latest PR i fixed all this with one caveat here which you spotted { theme } in oder to have it reactive in Vue way you just need to pass a reactive props , these props can be updated by the globals or any other effect

like this

// .storybook/withVuetifyTheme.decorator.js
import { h , shallowReactive} from 'vue';
import StoryWrapper from './VuetifyAppWrapper.vue';

export const DEFAULT_THEME = 'light';

const theme = shallowReactive({themeName:DEFAULT_THEME});

export const withVuetifyTheme = (storyFn, context) => {
  // Pull our global theme variable, fallback to DEFAULT_THEME
  theme.themeName =  context.globals.theme || DEFAULT_THEME;
  const story = storyFn();

  return () => {
    return h(
      StoryWrapper,
      // pass a reactive props contains themeName to StoryWrapper 
      // make sure you don't destruct this 👍  if you like to keep reactivity
      theme,
      {
        story: () => h(story, { ...context.args }),
      }
    );
  };
};

@chakAs3
Copy link
Contributor

chakAs3 commented Apr 1, 2023

@shilman @kasperpeulen
i just resolved @storybook/vue3 to my local fork to show them it is working fine in #21273.
is still there any reason why we are not merging it?

@chakAs3 chakAs3 self-assigned this Apr 1, 2023
@shilman
Copy link
Member

shilman commented Apr 19, 2023

Yippee!! I just released https://github.com/storybookjs/storybook/releases/tag/v7.1.0-alpha.7 containing PR #21954 that references this issue. Upgrade today to the @future NPM tag to try it out!

npx sb@next upgrade --tag future

Closing this issue. Please re-open if you think there's still more to do.

@Joey-J3
Copy link

Joey-J3 commented Mar 25, 2024

I ran into a similar issue while observing the global variable 'theme' in version 7.5.2 with [email protected].

(story, ctx) => {
      const { theme = 'light' } = ctx.globals
      let template = '<story />'
      watchEffect(() => {
        if (theme === 'light') {
          template = '<story />'
        }
        if (theme === 'dark') {
          template = '<div class="dark-mode"><story /></div>'
        }
        if (theme === 'side-by-side') {
          template = `<div style="display: flex"><div style="flex: 1"><story /></div><div style="flex: 1" class="dark-mode"><story /></div></div>`
        }
      })
      return {
        components: { story },
        template
      }
    }

Do you have any suggestions on how to resolve this?

@kasperpeulen
Copy link
Contributor

@Joey-J3 We only fixed this for Vue3, and have deprecated the Vue2 framework in 8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

No branches or pull requests

6 participants