Blueprint: PanelStack does not re-render on props change

Created on 21 Nov 2018  路  5Comments  路  Source: palantir/blueprint

Environment

  • __Package version(s)__: 3.9.0
  • __Browser and OS versions__: Chrome Version 70.0.3538.102 (Official Build) (64-bit), MacOS 10.14.1

Steps to reproduce

  1. Visit https://codesandbox.io/s/8y15oovmj
  2. Click on the button

Actual behavior

The count on top is updated but not inside the PanelStack

Expected behavior

The count should be updated at both places.

Possible solution

Not sure, but I think passing in the props directly to PanelView here will cause it to re-render

core bug help wanted

Most helpful comment

Thanks for responding to this. The explanation does make sense and using connected panels is definitely a good option.

Apart from making component controlled, I think I have some other ideas, I'll work a bit and share them.

All 5 comments

this is a known limitation of PanelStack. definitely open to reviewing a fix PR @anawaz42. your possible solution seems workable.

This is indeed a known limitation. Your idea, however, will not fix it. The initialPanel is put on the React state of the PanelStack only once, any changes to the prop are ignored after that. The fact that the PanelView doesn't re-render is not relevant here.

There is currently no way to ever change the props of an open panel. We internally use redux-connected panels to make them react to changes in the app.
The only possible solution I see is to allow making the PanelStack a controlled component, so you can maintain the entire stack of panels as you wish, including changes to existing panels' props.

Thanks for responding to this. The explanation does make sense and using connected panels is definitely a good option.

Apart from making component controlled, I think I have some other ideas, I'll work a bit and share them.

I found a very good solution!

Use a React <Context> instead.

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

https://reactjs.org/docs/context.html

This is my wrapper component:

const Context = React.createContext({} as MenubarProps);

function Menubar(props: MenubarProps) {
  const [panels, setPanels] = useState<IPanel<{}>[]>([
    {
      component: Settings,
      props: {},
      title: "Settings",
    },
  ]);

  return (
    <Context.Provider value={props}>
      <PanelStack
        className="Menubar"
        initialPanel={panels[0]}
        onOpen={(new_) => setPanels([new_, ...panels])}
        onClose={() => setPanels(panels.slice(1))}
      />
    </Context.Provider>
  );
}

A helper to automatically wrap the panel components:

const withContext = (Component: (props: PanelProps) => any) => (panelProps: any) => (
  <Context.Consumer>{(props) => <Component {...props} {...panelProps} />}</Context.Consumer>
);

Example of one of the panels:

const SelectNetwork = withContext(({
    closePanel, // <--- from PanelStack's props
    setNetwork, ap, setAp, // <--- my custom props
   }: PanelProps) => {

  return (
    <CaptivePortal
      onConnected={(essid) => {
        closePanel();
        setNetwork(essid);
      }}
      onAP={() => {
        closePanel();
        setNetwork("");
      }}
      ap={ap}
      setAp={setAp}
      vertical
    />
  );
});

Type:

type PanelProps = IPanelProps & MenubarProps; // PanelStack's props + my custom props

Closing this old issue as it appears it can be worked around with controlled mode (#3601) and there are other approaches to data management (redux, react context, etc.) which allow you to connect indirectly to component trees.

Was this page helpful?
0 / 5 - 0 ratings