Skip to content

Commit

Permalink
[core] fix(PanelStack2): more stable controlled mode (#4808)
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya authored Jul 15, 2021
1 parent 713d524 commit 03c6cae
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 182 deletions.
2 changes: 2 additions & 0 deletions packages/core/src/components/panel-stack2/panel-stack2.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ const SettingsPanel: React.FC<PanelProps<SettingsPanelInfo>> = props => {

PanelStack2 can be operated as a controlled or uncontrolled component.

If controlled, panels should be added to and removed from the _end_ of the `stack` array.

@interface PanelStack2Props
9 changes: 5 additions & 4 deletions packages/core/src/components/panel-stack2/panelStack2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ export const PanelStack2: PanelStack2Component = <T extends Panel<object>>(props
stackLength.current = stack.length;
}, [stack]);

if (stack.length === 0) {
return null;
}

const handlePanelOpen = React.useCallback(
(panel: T) => {
props.onOpen?.(panel);
Expand All @@ -130,6 +126,11 @@ export const PanelStack2: PanelStack2Component = <T extends Panel<object>>(props
[stack, props.onClose],
);

// early return, after all hooks are called
if (stack.length === 0) {
return null;
}

const panelsToRender = renderActivePanelOnly ? [stack[0]] : stack;
const panels = panelsToRender
.map((panel: T, index: number) => {
Expand Down
30 changes: 19 additions & 11 deletions packages/core/src/components/panel-stack2/panelView2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ export const PanelView2: PanelView2Component = <T extends Panel<object>>(props:
/>
);

// `props.panel.renderPanel` is simply a function that returns a JSX.Element. It may be an FC which
// uses hooks. In order to avoid React errors due to inconsistent hook calls, we must encapsulate
// those hooks with their own lifecycle through a very simple wrapper component.

// N.B. A type cast is required because of error TS2345, where technically `panel.props` could be
// instantiated with a type unrelated to our generic constraint `T` here. We know
// we're sending the right values here though, and it makes the consumer API for this
// component type safe, so it's ok to do this...
const PanelWrapper: React.FunctionComponent = React.useMemo(
() => () =>
props.panel.renderPanel({
closePanel: handleClose,
openPanel: props.onOpen,
...props.panel.props,
} as PanelProps<T>),
[props.panel.renderPanel, handleClose, props.onOpen, props.panel.props],
);

return (
<div className={Classes.PANEL_STACK2_VIEW}>
{props.showHeader && (
Expand All @@ -80,17 +98,7 @@ export const PanelView2: PanelView2Component = <T extends Panel<object>>(props:
<span />
</div>
)}
{/*
* Cast is required because of error TS2345, where technically `panel.props` could be
* instantiated with a type unrelated to our generic constraint `T` here. We know
* we're sending the right values here though, and it makes the consumer API for this
* component type safe, so it's ok to do this...
*/}
{props.panel.renderPanel({
closePanel: handleClose,
openPanel: props.onOpen,
...props.panel.props,
} as PanelProps<T>)}
<PanelWrapper />
</div>
);
};
Expand Down
Loading

1 comment on commit 03c6cae

@blueprint-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[core] fix(PanelStack2): more stable controlled mode (#4808)

Previews: documentation | landing | table

Please sign in to comment.