<Collapse> component with isOpen={true}The Collapse component is not initially open and content within does not show. You have to transition isOpen -> closed (isOpen=false) -> open (isOpen=true) on the component to "jig" the internal component state for the content within to then properly show
The component should already start and show in an expanded state if isOpen is initially true
@jumpinjackie I just tried to reproduce this but it's working fine for me. Are there any more information that might help debug?
@leebyp I'll try to get a minimal example for you as soon as possible. But in the meantime, if this helps in anyway: I'm using Collapse to build an Accordion component, which renders out a list of Collapse child components laid out like so:
It is under this configuration that the last collapse doesn't initially expand on first render.
Ok I think I've untangled my accordion implementation to the following code sample (TypeScript 2.1)
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Collapse } from "@blueprintjs/core";
export interface IAccordionPanelContentDimensions {
width: number;
height: number;
}
export interface IAccordionPanelSpec {
id: string;
title: string;
contentRenderer: (dim: IAccordionPanelContentDimensions) => JSX.Element;
}
export interface IBuggyAccordionProps {
dim: IAccordionPanelContentDimensions;
style?: React.CSSProperties;
panels: IAccordionPanelSpec[];
}
const PANEL_HEADER_HEIGHT = 24;
export class BuggyAccordion extends React.Component<IBuggyAccordionProps, any> {
private fnTogglePanel: GenericEventHandler;
constructor(props: IBuggyAccordionProps) {
super(props);
this.fnTogglePanel = this.onTogglePanel.bind(this);
this.state = {
openPanel: null
};
}
private onTogglePanel(e: GenericEvent) {
const id = e.currentTarget.attributes["data-accordion-panel-id"].value;
if (this.state.openPanel != id) {
this.setState({ openPanel: id });
} else {
this.setState({ openPanel: null });
}
}
componentDidMount() {
this.setState({
openPanel: this.props.panels[this.props.panels.length - 1].id
});
}
render(): JSX.Element {
const { openPanel } = this.state;
const { panels, style, dim } = this.props;
return <div style={style} className="component-accordion">
{panels.map(p => {
const isOpen = (p.id == openPanel);
return <div key={p.id} className="component-accordion-panel">
<div className="component-accordion-panel-header" style={{ height: PANEL_HEADER_HEIGHT }} data-accordion-panel-id={p.id} onClick={this.fnTogglePanel}>
<span className={`pt-icon-standard pt-icon-chevron-${isOpen ? "up" : "down"}`}></span> {p.title}
</div>
<Collapse isOpen={isOpen}>
{p.contentRenderer({ width: dim.width, height: (dim.height - (panels.length * PANEL_HEADER_HEIGHT)) })}
</Collapse>
</div>;
})}
</div>;
}
}
export function testAccordion(el: Element) {
const panels: IAccordionPanelSpec[] = [
{
id: "Foo",
title: "Foo",
contentRenderer: (dim) => <div style={{ width: dim.width, height: dim.height, backgroundColor: "red", color: "white" }}><p>Foo</p></div>
},
{
id: "Bar",
title: "Bar",
contentRenderer: (dim) => <div style={{ width: dim.width, height: dim.height, backgroundColor: "green", color: "white" }}><p>Bar</p></div>
},
{
id: "Baz",
title: "Baz",
contentRenderer: (dim) => <div style={{ width: dim.width, height: dim.height, backgroundColor: "blue", color: "white" }}><p>Baz</p></div>
}
];
ReactDOM.render(<BuggyAccordion dim={{ width: 250, height: 600 }} panels={panels} />, el);
}
The last panel (indicated by the header and content of "Baz" with a blue background) should be initially expanded. But I have to expand/collapse/expand to then have that panel property showing properly.
After that, it then starts to behave like a proper accordion where expanding one collapses the others.
@jumpinjackie Thanks for putting that together, very helpful! There's an edge case here creeping into the lifecycle events of Collapse because componentDidMount gets called in the children before the parent. So the bug is actually in your usage, not in the component itself.
You have two choices to fix this. From your example you can either:
constructor instead of componentDidMount since you're only depending on the props (I prefer this myself),componentWillMount instead of DidMount if your use case is more complicated(edited comment above for clarity)
@leebyp Yes! Setting initial state from componentDidMount to constructor makes the accordion functional as intended. Thanks!
I have no further issues. Feel free to close if this is "by design"
Most helpful comment
@leebyp I'll try to get a minimal example for you as soon as possible. But in the meantime, if this helps in anyway: I'm using Collapse to build an Accordion component, which renders out a list of Collapse child components laid out like so:
It is under this configuration that the last collapse doesn't initially expand on first render.