storybookjs / storybook

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

[Bug]: 7.3.2 When providing a light / dark theme via context, every story switch or docs switch resets the context to the initial value #24088

Closed martinburford closed 1 year ago

martinburford commented 1 year ago

I am using MDX for stories within Storybook 7.3.2. I have found the globalTypes to be incredibly flakey, often not working at all, so I decided to add my own React Context Provider within preview.tsx ...

const preview: Preview = {
  decorators: [withThemeProvider],
  ...
};

This is a super simple provider

"use client";

import { ThemeContextProvider } from "./theme";

export function Providers({ children }: { children: React.ReactNode }) {
  return <ThemeContextProvider>{children}</ThemeContextProvider>;
}
// NPM imports
import React, { createContext, useState } from "react";

// Construct the initial context
const ThemeContext = createContext({
  theme: "light", // "light" || "dark"
  toggleTheme: function () {},
});

/**
 * @function ThemeContextProvider - The Provider for the "Theme" context
 * @param {object} props - All props provided to the Provider (this will only ever be children in this case)
 * @returns {provider} The provider holding the context, defining the current theme
 */
export function ThemeContextProvider({ children }) {
  const [theme, updateTheme] = useState("light");

  // The toggle handler to switch the theme
  function toggleThemeHandler() {
    updateTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
  }

  // Construct the context to be passed down via the Provider
  const context = {
    theme,
    toggleTheme: toggleThemeHandler,
  };

  return <ThemeContext.Provider value={context}>{children}</ThemeContext.Provider>;
}

export default ThemeContext;

Within a component shown within EVERY storyfile, I then do the following:

const { theme, toggleTheme } = useContext(ThemeContext);

useEffect(() => {
  document.querySelector("html").setAttribute("data-theme", theme);
}, [theme]);

When the above functionality is executed (via a simple button click) ... sure enough, the context is updated, so that the contexts theme property is either light or dark.

<Button
  ...
  onClick={() => toggleTheme()}
  ...
>
  Light
</Button>
<Button
  ...
  onClick={() => toggleTheme()}
  ...
>
  Dark
</Button>

What I'm finding however is that whenever a new story is changed OR the docs page is viewed for any story, the default value of light is restored, presumably because the provider is also re-generated as part of an iframe re-generation / refresh, wiping out any previous context values.

As mentioned, I'm only using this approach as using globalTypes hasn't really worked for me. I used it absolutely as per the Storybook docs, and I'd say it worked 40-60% of the time, sometimes switching the global type, other times, it was ignored. If this approach worked, I'd happily use it, but it looks a bit buggy from what I've seen. I'm not sure if this is a known bug, but it's this reason why I've looked to alternative solutions.

Here is a video demonstrating the issue:

https://youtu.be/ObOm67mWTZU

To Reproduce

The Storybook instance where this is happening can be found here:

https://storybook.martinburford.co.uk/?path=/docs/components-atoms-card--docs

With the code being here:

https://github.com/martinburford/www-nextjs13

System

npx storybook@latest info
npm ERR! cb.apply is not a function

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/martinburford/.npm/_logs/2023-09-06T11_15_05_624Z-debug.log
Install for [ 'storybook@latest' ] failed with code 1

With the log file showing the following:

0 info it worked if it ends with ok
1 warn npm npm does not support Node.js v16.14.0
2 warn npm You should probably upgrade to a newer version of node as we
3 warn npm can't make any promises that npm will work with this version.
4 warn npm Supported releases of Node.js are the latest release of 6, 8, 9, 10, 11, 12.
5 warn npm You can find the latest version at https://nodejs.org/
6 verbose cli [
6 verbose cli   '/usr/local/bin/node',
6 verbose cli   '/Users/martinburford/.nvm/versions/node/v11.10.1/lib/node_modules/npm/bin/npm-cli.js',
6 verbose cli   'install',
6 verbose cli   'storybook@latest',
6 verbose cli   '--global',
6 verbose cli   '--prefix',
6 verbose cli   '/Users/martinburford/.npm/_npx/19050',
6 verbose cli   '--loglevel',
6 verbose cli   'error',
6 verbose cli   '--json'
6 verbose cli ]
7 info using npm@6.7.0
8 info using node@v16.14.0
9 verbose npm-session 24579efb59c38123
10 silly install loadCurrentTree
11 silly install readGlobalPackageData
12 http fetch GET 200 https://registry.npmjs.org/storybook 178ms
13 http fetch GET 200 https://registry.npmjs.org/storybook/-/storybook-7.4.0.tgz 64ms
14 silly pacote tag manifest for storybook@latest fetched in 255ms
15 verbose stack TypeError: cb.apply is not a function
15 verbose stack     at /Users/martinburford/.nvm/versions/node/v11.10.1/lib/node_modules/npm/node_modules/graceful-fs/polyfills.js:285:20
15 verbose stack     at FSReqCallback.oncomplete (node:fs:199:5)
16 verbose cwd /Users/martinburford/Sites/personal-projects/martinburford.co.uk-nextjs13
17 verbose Darwin 21.5.0
18 verbose argv "/usr/local/bin/node" "/Users/martinburford/.nvm/versions/node/v11.10.1/lib/node_modules/npm/bin/npm-cli.js" "install" "storybook@latest" "--global" "--prefix" "/Users/martinburford/.npm/_npx/19050" "--loglevel" "error" "--json"
19 verbose node v16.14.0
20 verbose npm  v6.7.0
21 error cb.apply is not a function
22 verbose exit [ 1, true ]


### Additional context

The sole aim of what I'm trying to do is to be able to switch a theme between `light` and `dark` mode, and for that to be used globally across any story or docs file I then subsequently view
martinburford commented 1 year ago

Here is an example of it not working when using globalTypes:

https://youtu.be/SKtb51AdRBQ

To achieve this, I've setup global types like this (within preview.tsx):

// Create a custom toolbar in Storybook, so that light and dark mode can be switched on-demand (AND persist between docs and canvas blocks)
export const globalTypes = {
  dataTheme: {
    defaultValue: "light",
    toolbar: {
      dynamicTitle: true,
      items: [
        { icon: "moon", value: "dark", title: "Dark mode" },
        { icon: "sun", value: "light", title: "Light mode" },
      ]
    }
  },
};

I also use this in combination with storybook-addon-data-theme-switcher which is referenced within main.ts:

const config: StorybookConfig = {
  stories: ["components/**/*.stories.mdx", "../components/**/*.stories.mdx"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
    "storybook-addon-data-theme-switcher",
  ],
  ...

Since I use a data attribute on the html node of either data-theme="light" or data-theme="dark", I'd expect this to be being updated every time the toolbar selection is changed, but it's not.

Is there something I'm doing wrong? Sometimes it does work, but it's 50/50. Sometimes it does, sometimes it doesn't, and just gets stuck on the the same thing, regardless of the selection you make (as per the video in this comment).

Any help with this would be appreciated.

Many thanks Martin

vanessayuenn commented 1 year ago

Hi @martinburford, the behaviour you described -- context resetting between pages -- is the intended behaviour indeed.

The globalTypes approach should worked, though. Have you tried @storybook/addon-themes instead? If that still doesn't work, please feel free to report back!

github-actions[bot] commented 1 year ago

Hi there! Thank you for opening this issue, but it has been marked as stale because we need more information to move forward. Could you please provide us with the requested reproduction or additional information that could help us better understand the problem? We'd love to resolve this issue, but we can't do it without your help!

github-actions[bot] commented 1 year ago

I'm afraid we need to close this issue for now, since we can't take any action without the requested reproduction or additional information. But please don't hesitate to open a new issue if the problem persists – we're always happy to help. Thanks so much for your understanding.