Is there a way to programmatically change w
and h
of an item's layout? The use case is to have a "collapse" button which reduces the height to a constant height, enough to leave the header of the item. To do this my original idea was to keep layouts
in the state of the component and manually change the height of the collapsed item/s to another constant height.
However it seems as though the library will ignore changes to layout
after initial rendering. Is this the case or is it a bug on my end? And if it's normal behavior, is there another way to change heights programmatically?
Here's a standalone component that implements the react-grid-layout
. It's two "widgets" that have an onClick handler to "collapse" them. By setting the state, it triggers the re-render and recalculates layouts so that any collapsed items have a reduced height. The console log statments show that the rendered components have the correct new layout but it does not get reflected on the screen, leading me to believe there is another reference to the height.
jsFiddle example
import React, { Component } from 'react';
import GridLayout, { WidthProvider } from 'react-grid-layout';
const Grid = WidthProvider(GridLayout);
// # WidgetsContainer
// Container WidgetsContainer component.
class WidgetsContainer extends Component {
static defaultProps = {
isDraggable: true,
isResizable: true,
rowHeight: 1,
cols: 12,
}
constructor(props) {
super(props);
this.state = {
layouts: [
{
i: 'item_1',
x: 0,
y: 0,
w: 5,
h: 25,
}, {
i: 'item_2',
x: 5,
y: 0,
w: 7,
h: 30,
},
],
collapsedWidgets: {},
};
}
toggleWidget(id) {
return () => {
const newState = {...this.state.collapsedWidgets};
const collapsed = typeof newState[id] === 'boolean' ? newState[id] : false;
newState[id] = !collapsed;
this.setState({
collapsedWidgets: newState,
});
}
}
onResize(layouts) {
this.setState({
layouts,
});
}
getModifiedLayouts() {
const { layouts, collapsedWidgets } = this.state;
const newLayouts = layouts.map(layout => {
const newLayout = { ...layout };
if (collapsedWidgets[newLayout.i]) {
newLayout.h = 5;
}
return newLayout;
});
return newLayouts;
}
getWidgets() {
const widgets = [{
component: <div style={{ height: '250px', background: 'lightgray' }}>Content</div>,
title: 'Item 1',
id: 'item_1',
}, {
component: <div style={{ height: '310px', background: 'lightgray' }}>Content 2</div>,
title: 'Item 2',
id: 'item_2',
}];
return widgets;
}
generateDOM() {
const widgets = this.getWidgets();
const modifiedLayouts = this.getModifiedLayouts();
return widgets.map((widget, i) => {
return (<div key={i} _grid={modifiedLayouts[i]}>
<div style={{ background: 'gray' }} onClick={::this.toggleWidget(widget.id)}>
{widget.title}
{widget.component}
</div>
</div>);
});
}
render() {
const widgets = this.generateDOM();
console.log(widgets[0].props._grid)
return (<div style={{ marginTop: '15px' }}>
{widgets ? <Grid className="layout"
{...this.props}
onResizeStop={::this.onResize}
>
{widgets}
</Grid> : null}
</div>);
}
}
export default WidgetsContainer;
It turns out the trick is to not use the _grid
and instead use the layout
prop on the Grid
component. Here is a working jsfiddle:
http://jsfiddle.net/zekedroid/d9o75d24/
And the code:
const Grid = ReactGridLayout.WidthProvider(ReactGridLayout);
class Logo extends React.Component {
constructor(props) {
super(props);
this.state = {
layouts: [
{
i: '0',
x: 0,
y: 0,
w: 5,
h: 25,
}, {
i: '1',
x: 5,
y: 0,
w: 7,
h: 30,
},
],
collapsedWidgets: {},
};
}
toggleWidget(id) {
return () => {
const newState = {...this.state.collapsedWidgets};
const collapsed = typeof newState[id] === 'boolean' ? newState[id] : false;
newState[id] = !collapsed;
this.setState({
collapsedWidgets: newState,
});
}
}
onResize(layouts) {
this.setState({
layouts,
});
}
getModifiedLayouts() {
const { layouts, collapsedWidgets } = this.state;
const newLayouts = layouts.map(layout => {
const newLayout = { ...layout };
if (collapsedWidgets[newLayout.i]) {
newLayout.h = 5;
}
return newLayout;
});
return newLayouts;
}
getWidgets() {
const widgets = [{
component: <div style={{ height: '250px', background: 'lightgray' }}>Content</div>,
title: 'Item 1',
id: '0',
}, {
component: <div style={{ height: '310px', background: 'lightgray' }}>Content 2</div>,
title: 'Item 2',
id: '1',
}];
return widgets;
}
generateDOM() {
const widgets = this.getWidgets();
return widgets.map((widget, i) => {
return (<div key={i} style={{ overflowY: 'auto' }}>
<div style={{ background: 'gray' }} onClick={this.toggleWidget(widget.id).bind(this)}>
{widget.title}
{widget.component}
</div>
</div>);
});
}
render() {
const widgets = this.generateDOM();
const modifiedLayouts = this.getModifiedLayouts();
return (<div style={{ marginTop: '15px' }}>
{widgets ? <Grid className="layout"
{...this.props}
onResizeStop={this.onResize.bind(this)}
layout={modifiedLayouts}
>
{widgets}
</Grid> : null}
</div>);
}
}
Logo.defaultProps = {
isDraggable: true,
isResizable: true,
rowHeight: 1,
cols: 12,
}
React.render( <Logo /> , document.getElementById('container'));
Thanks to @aepp's elaboration on Github, this solution works just fine, I've tested this using [email protected]
:
import React, {Component} from 'react';
import GridLayout from 'react-grid-layout';
...
//defining the layout inside the "state"
constructor(props) {
super(props);
this.state = {
layout: [
...your_grid_items
],
};
}
...
resizeGridItem = () => {
const layout = this.state.layout.map(item => {...item});
//here you can modify your layout, i.e.
//layout[4].h = 4;
...
this.setState({layout});
}
...
render() {
return (
<div>
<GridLayout
layout={this.state.layout}
...
>
...
</GridLayout>
...
<button onClick={this.resizeGridItem}>Resize</button>
</div>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With