-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React: Fix decorators to conditionally render children #22336
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this seems reasonable given my (limited) understanding of the motivation of the hook restrictions. Similar to the way that React can scope hooks to a single component, we can scope hooks to a single decorator, correct?
@redbugz -- I think it would be good to add a "template story" to preview-api
and a test (either e2e or just an interaction test on the story) that demonstrates the error no longer occurs.
Sure I can give that a try. I'm still learning this codebase so I'm not sure where that would go. Poking around, would it be adding a story to https://github.com/storybookjs/storybook/blob/next/code/renderers/react/template/stories/decorators.stories.tsx or https://github.com/storybookjs/storybook/blob/next/code/renderers/react/template/stories/hooks.stories.tsx or a new file in that location or a different location like adding a templates/stories dir inside of https://github.com/storybookjs/storybook/tree/next/code/lib/preview-api? Would an e2e test go here https://github.com/storybookjs/storybook/blob/next/code/e2e-tests/preview-web.spec.ts or somewhere else? |
Hey @redbugz sorry if it wasn't clear but I posted some advice in discord, repeating here for clarity:
I'm actually confused why the current preview stories live in For now, in that
It would, but I am not sure an e2e test is needed, after writing my comment above I thought a bit more and I think we can get what we need with just a play function. Usually we just use e2e tests to do test the interaction between manager + preview, whereas if we just need to check behaviour when things like args change, we can get away with just an "integration" test (a template story that is tested by Chromatic + the test runner), not a full fledged playwright e2e test. |
I was minimizing effort at the time @tmeasday. Moving all that code around was complex enough as it is, and so I didn't feel the need to also change that part. But it makes perfect sense to move those templates to the preview-api package instead, especially considering we're hoping to remove the shim packages next major release. |
@redbugz any luck with this? I can take a look if you are stuck. |
@redbugz I'm happy to merge this if you cherry pick that test across (and it passes :) ) |
Hmm. It looks like returning |
@kasperpeulen @chakAs3 I'll note that I looked at this story in vue3 and it isn't actually possible to conditionally render there. Even with the story in this PR, the second decorator always gets rendered. It won't break this particular test, but it's not consistent with the other renderers. We should probably write some proper stories/tests for decorator behaviour like this, and get all renderers to conform to a common contract for sanity's sake. |
Hi @tmeasday you are right I think we should skip this for vue ( renderer they don't use useEffect hook ). |
@shilman @tmeasday @ndelangen is it normal for this PR to have over 1500 files changed? |
@chakAs3 I think you misunderstand. This is not testing vue hooks, it is testing the storybook hooks which should work for every framework. So I wouldn't want to skip the test in vue. My problem is actually that the test won't fail prior to this change in vue because it isn't possible to actually conditionally render in a decorator in the first place. This seems wrong to me but is out of scope of this PR.
I disagree. It should be possible to not render the story at all in a decorator. I made it work, but the interesting (broken?) part is the intervening decorator (which isn't rendered) still gets called.
Definitely not, something weird has happened when I updated the branch. |
1a06e9c
to
097fe0a
Compare
Fixes storybookjs#15223 Custom preview-api hooks assumed that all decorators were always rendered however if a custom decorator conditionally rendered it's children, then not all decorators would get rendered, especially jsxDecorator. This would result in the error "Rendered more hooks than during the previous render." This removes the assumption that all decorators render every time and relies on each decorator to register itself during MOUNT phase which is handled when each decorator goes through `hookify`
097fe0a
to
213117b
Compare
I agree with you, I was just telling you the current state, of course, you can choose to not render the story or render whatever you want I even demoed it to @shilman, and @kasperpeulen. but you need to return nonnull value ( ), But as you said it is something that we should revise in storybook decorator's implementation maybe in another PR. My take is to cancel a decorator we should not have the condition inside the decorator itself because even if the decorator returns null, still we ran it first. decorators = [ decoratorFn1() , decoratorFn2(), decoratorFn3() .....] my solution if it was just Vue we can run extra cycle that checks valid decorators to run the ones that don't return null decorators = [ decoratorFn2(), decoratorFn3() ] But I think in React will break if you use some hooks. NB:I dealt with these storybook hooks , they are around 9 that handle background, grid... they have useEffect hooks with caused a problem for me these decorators, i can't call them outside of the render cycle. |
@chakAs3 the idea is a decorator returns what should be rendered. In react it is valid to return Alternatively, the decorator can call the passed IMO the next decorator in the chain should not get executed if you don't call that
If you render the next decorator like
Right, and I think I called out that the decorators being called "outside rendering" was going to be an issue on that PR. |
Sorry, I'm back from vacation now, thanks to everyone for helping. I'm still trying to understand how the repo works, I have the latest code with the new template story. I have a few days I can help now, so willing to do whatever I can to help finish things up. When I create a new sandbox, I am not getting the new templated story in that sandbox, even after I do all the steps in It's also not clear to me how the play function is executed by tests, or even how to run the tests locally against these templated stories, and you mentioned a chromatic test or an interaction test which I'm not familiar with and I get an error trying to run the chromatic tests locally. Do those just run during CI? Also all of the CI checks seem to be failing on storyshots-puppeteer, not sure what I need to do to get those resolved, just continue to merge next into this branch? |
Hi @redbugz you should be able to view these stories if you just run
This is because only certain testing library commands are "instrumented" (so show up in the panel). Random stuff like emitting on the channel isn't. That's OK, the play function is still running.
Yes, Chromatic has run against all the sandboxes in CI. The interaction tests is just the play function. To run it, you can just navigate to the story.
Yeah the failures on this branch seem unrelated, it was working earlier, I'll dig into it. [1] This wasn't working because it looks like maybe @ndelangen moved the stories and there was a merge conflict. I fixed that. |
This story is just not rendering consistently across frameworks. @shilman I'm not sure how concerned to be. TLDR: it seems like decorators are a bit of an inconsistent mess between renderers. |
@shilman OK, thanks for bearing with me. So this story "decorators: Hooks" is supposed to render
|
@tmeasday I spoke with @kasperpeulen about it and he's going to take a quick look at the Vue failures. My gut says we should forget about the Vue2 case and diagnose the Vue3 case / fix it if it's an easy fix. |
@shilman @tmeasday I don't fully understand the decorator implementation of Vue. export function decorateStory(
storyFn: LegacyStoryFn<VueRenderer>,
decorators: DecoratorFunction<VueRenderer>[]
): LegacyStoryFn<VueRenderer> {
return decorators.reduce(
(decorated: LegacyStoryFn<VueRenderer>, decorator) => (context: StoryContext<VueRenderer>) => {
const innerStoryFn: PartialStoryFn<VueRenderer> = (update) => {
const sanitizedUpdate = sanitizeStoryContextUpdate(update);
// update the args in a reactive way
if (update) sanitizedUpdate.args = Object.assign(context.args, sanitizedUpdate.args);
return decorated({ ...context, ...sanitizedUpdate });
};
const decoratedStory: VueRenderer['storyResult'] = decorator(innerStoryFn, context);
const innerStory = () => h(innerStoryFn!);
if (!isVNode(decoratedStory) && typeof decoratedStory === 'object') {
decoratedStory.components = { ...decoratedStory.components, story: () => h(innerStoryFn) };
}
return prepare(decoratedStory, innerStory) as VueRenderer['storyResult'];
},
(context) => prepare(storyFn(context)) as LegacyStoryFn<VueRenderer>
);
} Which fixes the test, but a lot of other tests are broken. My gut feeling is that, the inner story is basically available in 2 ways, in Vue (by calling storyFn, but also by using I can investigate this further if you think that is prio @shilman. |
@kasperpeulen i'd say mark it with a FIXME and let's move on |
I guess my final question is what is the best way to "approve" the above 5 or so vue2/3 stories that are rendering "wrong" (or at least inconsistent with the other renderers)? It feels like we should comment somewhere about it so we don't get confused later. |
@shilman Probably also good to approve the chromatic snapshots with a comment to the github issue. |
Good call @kasperpeulen . Done! |
Hi. @tmeasday @shilman @kasperpeulen, |
React: Fix decorators to conditionally render children (cherry picked from commit 801c012)
Fixes #15223
Custom preview-api hooks assumed that all decorators were always rendered however if a custom decorator conditionally rendered it's children, then not all decorators would get rendered, especially jsxDecorator. This would result in the error
"Rendered more hooks than during the previous render." This removes the assumption that all decorators render every time and relies on each decorator to register itself during MOUNT phase which is handled when each decorator goes through
hookify
Closes #15223
What I did
Previously hooks.ts would always set hooks.prevMountedDecorators to the entire list of registered decorators, which assumed they would all render and mount. This changes that behavior to set that to an empty Set in
init
and only update that inapplyDecorators
if it's nullish.As each decorator goes through the MOUNT stage, then it will add itself to the
prevMountedDecorators
set so it will accurately only move to the UPDATE stage if MOUNT has already happened.There are a lot of comments at the bottom of #15223 detailing the process I went through to discover this and what I learned and how I came to this conclusion and fix.
How to test
See repro at https://github.com/marybeshaw/storybook-issue-15223
Started storybook with
yarn storybook
then any story will trigger the error, I used Button mostly.I couldn't get the instructions for yarn linking to work locally, so I checked out that repo, updated it to yarn 3 and storybook 7.1.0-alpha.0, then manuallly made the changes to node_modules/@storybook/preview/dist/runtime.js to verify the fix. Also used the unit test to confirm the desired behavior. Without the fix, the unit test fails with the "Rendered more hooks" error.
Also verified against repro: https://github.com/seppzero/sb-decorator-issue
Once again, couldn't get linking to work, so I checked out the repo, built it using the 6.5 version it had, ran storybook, the Button story fails with the "Rendered more hooks" error, manually patched node_modules/@storybook/addons/dist/esm/hooks.js , restarted storybook, now the Button story works fine
I don't believe any documentation needs to be updated for this.
Checklist
MIGRATION.MD
Maintainers
make sure to add the
ci:merged
orci:daily
GH label to it.["cleanup", "BREAKING CHANGE", "feature request", "bug", "documentation", "maintenance", "dependencies", "other"]