storybook: play function does not work after React 18

Screen Shot 2022-05-18 at 6 54 57 PM **Describe the bug** I created stories for react-hook-form validation and used play function. It worked well React 17. After I updated React from 17 to 18, play function does not trigger validation. Do I miss something? @yannbf
export const ValidationErrors = {
  render: (args) => (
    <>
      <h4>Superstruct validation</h4>
      <Form
        defaultValues={{
          radioField1: null,
        }}
        resolver={superstructResolver(RadioGroupStruct)}
        onSubmit={action('Superstruct submit')}
        mode="onSubmit"
      >
        <FormRadioGroup
          name="radioField1"
          label="Trigger validation"
          options={['One', 'Two', 'Three', 'Four']}
          {...args}
        />
        <FormSubmit>Save</FormSubmit>
      </Form>
      
      <h4 className="mt-4">RHF validation</h4>
      <Form
        defaultValues={{
          radioField1: null,
        }}
        onSubmit={action('RHF submit')}
        mode="onSubmit"
      >
        <FormRadioGroup
          name="radioField1"
          label="Trigger validation"
          options={['One', 'Two', 'Three', 'Four']}
          {...args}
          validation={{validate: atLeastOne}}
        />
        <FormSubmit>Save</FormSubmit>
      </Form>
    </>

  ),
  args: {
    readOnly: false,
    disabled: false,
    help: 'help text here',
  },
  play: async ({canvasElement}) => {
    const btnElems = within(canvasElement).getAllByRole('button', {name: /Save/i});

    // Superstruct validation: trigger radio group
    await userEvent.click(btnElems[0]);

    // RHF validation: trigger radio group
    await userEvent.click(btnElems[1]);
  },
};

Expected output Screen Shot 2022-05-18 at 6 57 47 PM

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 66 (38 by maintainers)

Most upvoted comments

Personally, I found that updating to the latest @storybook/testing-library (which uses user-event@14) solved it for us.

I see that @yannbf is assigned to this ticket, I think he might be the best to discuss the plans for it. I do hope it can be fixed soon, since it’s essentially blocking my upgrade to react 18 right now.

I can also confirm I’m experiencing this (or a very similar) issue after updating to react 18 (and storybook 7-alpha.33). I have the validation error problem as shown above, but also the first character is sometimes lost from await userEvent.type();. For instance:

await userEvent.type(input, '1234');

Results in 234 in the field. But if I add a 1ms sleep, then it’s fine, and the whole thing is typed correctly.

Similarly, a “combobox” of mine is not opening correctly when first clicked on, unless I add a sleep.

How does the play function know when to fire? IOW, where does storybook trigger the play function, and is there a chance that it needs to wait just a bit longer for react to finish rendering?

@yannbf I talked to Chromatic team and they shared me reference issue #19758 about play function does not work in stories built for Production and when I read this issue a couple of reasons trigger this problem and for us, we changed our build commands and it worked and I will summarize it here for people who will have this problem in the future.

  1. using NODE_ENV=development yarn build-storybook - We tried this and it worked.(Chromatic team shared it with us)
  2. use the latest storybook and matches all libraries version - We did not get latest one yet. Maybe this can be other solution.
  3. testing-library/react and @storybook/testing-library create conflict but we do not import both library in our stories. I ignore it. - This is your solution but we do not import them together so if someone do it, they can solve this conflict.

As a result, today we got our first successful chromatic build and play function trigger RHF(react-hook-form) validations without sleep fn.

Thank you for helping to fix this issue. @shilman @tmeasday @IanVS @bluebill1049 @yannbf

@IanVS sorry yes, sleep(1) makes it work locally on my MacBook. I had to increase it to sleep(1000) because the Jenkins where I work is slow and 1ms doesnt suffice sometimes.

Doh! I didn’t notice the reproduction was using an old version of storybook. I’ve updated it so that it will work in node 18 now. Sorry for the trouble, and thanks again so much for taking a look.

@shilman is there a timeline for this fix? The workaround does help, but we’ve had to use sleep(1000) to get the tests to reliably run and this time is starting to stack up.

I think that I have noticed smtg similar with the latest preact as well. I think Preact changed some of the internal (maybe related to 10.10). I will try to create a repro later when I have time (I am kind of lazily hoping that a preact update fixes it).

Thanks so much @reinertisa. No idea what the problem is yet, but this is a great starting point!

Thank you so much @reinertisa for trying all this out and providing information here! Given that the original error seems to be solved, I will be closing this issue. If you’d like you can write a new one discussing the act issue, which seems unrelated to React 18. Thank you!

I have started to experience this after upgrading React 17 to 18. In our case we have a useEffect that runs to update state to hide the sidebar.

const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
  if(hasSomeCondition) {
    setIsOpen(false);
  }
}, [hasSomeCondition[)

Our test has the following which the element has onClick={() => setIsOpen(true)}

await waitFor(() => userEvent.click(canvas.getByTestId('navtoggle')));

What is happening for us is that the user click event is happening before the useEffect has run when the component renders. Adding a sleep above solves the problem as the click will run after the useEffect has run.

@IanVS interestingly that reproduction itself is very intermittent–basically doesn’t happen for me apart from the very first render.

Would it be useful to put together a non-SB reproduction @bluebill1049? Have you tried that @IanVS?

OK, I put together a Stackblitz that shows the behavior. Thanks for your patience. https://stackblitz.com/edit/github-z8cetq?file=src/reacthookform.jsx.

Ah bugs upon bugs. Such is the fun with beta software. 😬

Yes I’ll try that, thanks. Just to double-check for now, are you performing a fresh yarn install? I’ll let you know when I have a stackblitz working, though.

yes, i can confirm that. rm -rf the entire node_module and fresh yarn

Unfortunately that didn’t help. I made the change, compiled it, copied it into my node_modules, and the problem still happens.

The reproduction isn’t as easy as I hoped, normal inputs work fine. ~I do have another component that uses a different library that’s also failing, but it might be a somewhat simpler case than react-hook-form, so I’ll try to put something together still.~. Nevermind, that component on its own is fine, but it fails when I use interactions on it when it’s inside a form. Definitely seems like this is some kind of interaction with react-hook-form, unfortunately. 😦

Oh, I do have a combobox (that uses downshift) that has this problem too, that might be a little simpler to demonstrate.

Perhaps the issue is that we are using useLayoutEffect rather than useEffect. I used that as suggested here, but it might be worth trying changing to useEffect and seeing if it helps @IanVS.

I will work on a reproduction that doesn’t use react-hook-form. I’ve been seeing the behavior when simply filling out inputs using testing-library methods, so hopefully it will not be hard to reproduce.

@reinertisa Clearly I need more coffee this morning! ☕

Ok, I’m able to reproduce the issue now (though intermittently – sometimes I see the validation error in React18 also!)

Can you please update your story code like this and verify that the problem goes away?

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export const Default = {
  render: (args) => <App {...args} />,
  play: async ({ canvasElement }) => {
    await sleep(0);
    await userEvent.click(within(canvasElement).getByRole('button', { name: /Submit/i }));
  },
};

It’s not a proper fix, but if it works for you that will help us diagnose the problem and work towards a proper solution. Thanks!