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 committed May 1, 2023
1 parent 68c2444 commit e54542e
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 e54542e

Please sign in to comment.