reactjsstorybookchromatic

Using addDecorator in Storybook's preview.ts throws Rendered more hooks than during the previous render


Reading through the resource loading documentation from Chromatic, the Solution B: Check fonts have loaded in a decorator section.

Mainly would like to load our fonts before rendering the stories. The solution suggest to use addDecorator where with a simple FC we can preload the fonts and once they are loaded it can render the stories with story().

See the suggested decorator for preview.ts:

import isChromatic from "chromatic/isChromatic";

if (isChromatic() && document.fonts) {
  addDecorator((story) => {
    const [isLoadingFonts, setIsLoadingFonts] = useState(true);

    useEffect(() => {
      Promise.all([document.fonts.load("1em Roboto")]).then(() =>
        setIsLoadingFonts(false)
      );
    }, []);

    return isLoadingFonts ? null : story();
  });
}

For some reason this throws the usual error when violating the Rules of Hooks:

Rendered more hooks than during the previous render

screenshot

What I've tried so far:

Mainly I tried to remove the useEffect which renders the stories:

if (isChromatic() && document.fonts) {
  addDecorator((story) => {
    const [isLoadingFonts, setIsLoadingFonts] = useState(true);
   
    return story();
  });
}

Also the error disappeared but the fonts are causing inconsistent changes our screenshot tests as before.

Question:

I don't really see any issues which would violate the Rules of Hooks in the added FC for the addDecorator.

Is there anything what can make this error disappear? I'm open to any suggestions. Maybe I missed something here, thank you!


Solution

  • Mainly what solved the issue on our end is removing from main.ts one of the addons called @storybook/addon-knobs.

    Also renamed from preview.ts to preview.tsx and used the decorator a bit differently as the following:

    export const decorators = [
      Story => {
        const [isLoadingFonts, setIsLoadingFonts] = useState(true)
    
        useEffect(() => {
          const call = async () => {
            await document.fonts.load('1em Roboto')
    
            setIsLoadingFonts(false)
          }
    
          call()
        }, [])
    
        return isLoadingFonts ? <>Fonts are loading...</> : <Story />
      },
    ]
    

    We dropped using the addDecorator and used as the exported const decorators as above.