Skip to content

Commit

Permalink
React decorators can conditionally render children
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
redbugz authored and tmeasday committed Jun 1, 2023
1 parent 3e61433 commit 9068e31
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 2 deletions.
4 changes: 2 additions & 2 deletions code/lib/preview-api/src/modules/addons/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class HooksContext<TRenderer extends Renderer, TArgs extends Args = Args>
init() {
this.hookListsMap = new WeakMap();
this.mountedDecorators = new Set();
this.prevMountedDecorators = this.mountedDecorators;
this.prevMountedDecorators = new Set();
this.currentHooks = [];
this.nextHookIndex = 0;
this.currentPhase = 'NONE';
Expand Down Expand Up @@ -191,7 +191,7 @@ export const applyHooks =
);
return (context) => {
const { hooks } = context as { hooks: HooksContext<TRenderer> };
hooks.prevMountedDecorators = hooks.mountedDecorators;
hooks.prevMountedDecorators ??= new Set();
hooks.mountedDecorators = new Set([storyFn, ...decorators]);
hooks.currentContext = context;
hooks.hasUpdates = false;
Expand Down
19 changes: 19 additions & 0 deletions code/lib/preview-api/src/modules/store/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ describe('Preview hooks', () => {
run(story, [decorator]);
expect(effect).toHaveBeenCalledTimes(1);
});
it('handles decorator conditionally rendering the story', () => {
const effect = jest.fn();
const story = () => {
useEffect(effect, []);
};
const decorator = (storyFn: any) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
setCounter((prevCounter) => prevCounter + 1);
}, [counter]);
if (counter % 2 === 1) storyFn();
return 'placeholder while waiting';
};
run(story, [decorator]);
run(story, [decorator]);
run(story, [decorator]);
run(story, [decorator]);
expect(effect).toHaveBeenCalledTimes(2);
});
it('retriggers the effect if some of the deps are changed', () => {
const effect = jest.fn();
let counter = 0;
Expand Down

0 comments on commit 9068e31

Please sign in to comment.