React: Fix source snippet decorator for story functions with suspense #17915
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
fixes #17839
In Storybook 6.4, if a story function triggers Suspense by throwing a Promise like some third party hooks do, the story will fail to render
because the useEffect is skipped the first time and then run the second time, resulting in the
"Rendered more hooks than during the previous render." error
This runs the story function after the useEffect hook so the same number of hooks are run whether it triggers suspense or not, and in keeping with the guidelines on not conditionally running hooks.
This is preventing us from upgrading to 6.4, so it would be nice if this were backported to the 6.4 release as well.
Example scenario:
There probably aren't too many hooks triggering Suspense yet, but we use one, and I imagine it will increase as React 18 and concurrent mode become more common, and especially once Suspense for Data Fetching is generally released.
We could wrap every story that uses this third-party hook in our own Suspense, but that seems tedious and can't be done with a decorator (we already have a decorator that wraps all our stories in Suspense, but this is called outside of that scope)
But that seems tedious and makes it harder to write clean stories.
Issue:
What I did
I moved the execution of the
storyFn
until after theuseEffect
so theuseEffect
will run even ifstoryFn
throws a Promise.I don't understand the scope and semantics of SNIPPET_RENDERED enough to know if the useEffect should skip emitting that event until the Suspense resolves, or if it would be better to wrap the StoryFn in a
<Suspense />
tag and provide some kind of fallback UI. Right now it will emit SNIPPET_RENDERED with an empty string until the Suspense resolves, then it will submit it again on the second render with the correct story content.How to test
If your answer is yes to any of these, please make sure to include it in your PR.