hipstersmoothie / storybook-dark-mode

A storybook addon that lets your users toggle between dark and light mode.
MIT License
428 stars 56 forks source link

Since v4.0.2 getting error "Storybook preview hooks can only be called inside decorators and story functions." #282

Open bcbailey-torus opened 2 weeks ago

bcbailey-torus commented 2 weeks ago

Summary

After upgrading to version 4.0.2 I am getting the error Storybook preview hooks can only be called inside decorators and story functions.

Downgrading to 4.0.1 makes the error goes away.

This is likely related to the changes in #279

Is there something I need to change on my end?

Full error

Error: Storybook preview hooks can only be called inside decorators and story functions.
    at invalidHooksError (http://localhost:6006/sb-preview/runtime.js:97:11863)
    at getHooksContextOrThrow (http://localhost:6006/sb-preview/runtime.js:97:12123)
    at useHook (http://localhost:6006/sb-preview/runtime.js:97:12204)
    at useMemoLike (http://localhost:6006/sb-preview/runtime.js:99:209)
    at useRefLike (http://localhost:6006/sb-preview/runtime.js:99:505)
    at useStateLike (http://localhost:6006/sb-preview/runtime.js:99:940)
    at useState (http://localhost:6006/sb-preview/runtime.js:99:1201)
    at useDarkMode (http://localhost:6006/node_modules/.cache/storybook/ee6bf224cd156d2a19aeb1406fef9897dbfe874b3eae8c467c24b2f62e4ecd1a/sb-vite/deps/storybook-dark-mode.js?v=2c4e06dd:8560:51)
    at DarkModeObserver (http://localhost:6006/.storybook/preview.tsx:11:18)
    at renderWithHooks (http://localhost:6006/node_modules/.cache/storybook/ee6bf224cd156d2a19aeb1406fef9897dbfe874b3eae8c467c24b2f62e4ecd1a/sb-vite/deps/chunk-ILFQ7OTE.js?v=2c4e06dd:11548:26)

Reference

My storybook .storybook/preview.tsx for reference:

import { useDarkMode } from 'storybook-dark-mode'
import { useColorScheme } from '@mui/material/styles'
import { useEffect } from 'react'
import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles'
import CssBaseline from '@mui/material/CssBaseline'

const DarkModeObserver = () => {
    const isDark = useDarkMode()
    const { setMode } = useColorScheme()
    useEffect(() => {
        if (isDark) {
            setMode('dark')
        } else {
            setMode('light')
        }
    }, [isDark])
    return null
}

const preview: Preview = {
    // ...
    decorators: [
        (Story) => (
            <CssVarsProvider theme={myThemeV1}>
                <DarkModeObserver />
                <CssBaseline />
                <Story />
            </CssVarsProvider>
        ),
    ],
}

export default preview

Versions

storybook: 8.1.10 storybook-dark-mode: 4.0.2

grubersjoe commented 1 week ago

If I understand #279 correctly this change was necessary to be compatible with @storybook/preview-api? My use case was to dynamically change the theme of the docs container in preview.tsx:

// .storybook/preview.tsx
const Container = (props: DocsContainerProps) => (
  <DocsContainer
    {...props}
    theme={{
      ...common,
      ...(useDarkMode() ? themes.dark : themes.light), // <<<
    }}
  />
);

Unfortunately, this broke with storybook-dark-mode@4.0.2. Does someone have an idea what to do about this? Is this hook simply no longer supported inside preview.tsx? Had the terrible idea to use a MutationObserver on the body class to work around this issue:

https://github.com/grubersjoe/react-activity-calendar/blob/main/.storybook/preview.tsx

I don't know why Storybook did these changes but they feel quite limiting =/.

valpioner commented 1 week ago

We can also reproduce this runtime error. It completely breaks our storybook docs theming approach. Can someone please fix it, or maybe suggest another way to change docs theme?

Meanwhile downgraded to 4.0.1 due to this breaking change.

// preview.ts
...
import { MyDocsContainer2 } from './docsContainer';

const preview: Preview = {
  parameters: {
    docs: {
      container: myDocsContainer
// myDocsContainer.tsx
...
import { useDarkMode } from 'storybook-dark-mode';

export const MyDocsContainer = (props: PropsWithChildren<DocsContainerProps>) => (
  <DocsContainer context={props.context} theme={useDarkMode() ? themes.dark : themes.light}>
    {props.children}
  </DocsContainer>
);
image
skaneprime commented 5 days ago

Facing same problem for dark mode in docs

    docs: {
      container: (props) => {
        const isDark = useDarkMode();
        const currentProps = { ...props };
        currentProps.theme = isDark ? themes.dark : themes.light;
        return React.createElement(DocsContainer, currentProps);
      },
    },

image

skaneprime commented 4 days ago

Found workaround for dark mode


const preview: Preview = {
    docs: {
      container: (props: {
        children: React.ReactNode;
        context: DocsContextProps;
        theme?: ThemeVars;
      }) => {
        // eslint-disable-next-line react-hooks/rules-of-hooks
        const [isDark, setDark] = React.useState(true);
        props.context.channel.on(DARK_MODE_EVENT_NAME, (state) =>
          setDark(state)
        );
        const currentProps = { ...props };
        currentProps.theme = isDark ? themes.dark : themes.light;
        return <DocsContainer {...currentProps} />;
      },
}
skaneprime commented 4 days ago

More a bit cleaner pragmatic solution in preview.tsx

const MyDocsContainer = (props: {
        children: React.ReactNode;
        context: DocsContextProps;
        theme?: ThemeVars;
      }) => {
        const [isDark, setDark] = React.useState(true);

        React.useEffect(() => {
          props.context.channel.on(DARK_MODE_EVENT_NAME, setDark);

          return () =>
            props.context.channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
        }, [props.context.channel]);

        return (
          <DocsContainer
            {...props}
            theme={isDark ? themes.dark : themes.light}
          />
        );
      }
grubersjoe commented 2 days ago

I mean it's a hack, but that's a lot better than what I came up with spontaneously :D. Thanks for sharing!