tl;dr:
React-grid-layout incorrectly moves all grid items around when their static option is enabled and disabled (toggled). I want the grid items to be frozen in place when static, and move if and only if the user moves them with static mode disabled. An example of the problem can be found here (CodeSandbox), with a GIF example here (Imgur).
Background
I am making an interactive grid layout for my app using react-grid-layout. I have a button to toggle between non-static (draggable and resizeable) and static (non-interactive) mode for all items in the grid (info about static in react-grid-layout). I am doing so with useState
and with tips from this answer:
var [layout, setLayout] = useState([
{ i: "a", x: 0, y: 0, w: 10, h: 4, static: false },
{ i: "b", x: 1, y: 0, w: 3, h: 2, static: false },
{ i: "c", x: 4, y: 0, w: 1, h: 2, static: false },
]);
function toggleStaticMode() {
setLayout(
layout.map((prevLayout) => {
return { ...prevLayout, static: !prevLayout.static };
})
);
}
Expected behavior:
Each element in the grid has unique properties that define its location, size, etc... on the grid layout. Those are defined in the layout
variable shown above. When toggling between static and non-static mode, all values should stay the same, except for the static
key in each object. The positions and sizes on-screen should stay the same as well. This enables a user to toggle static mode off to change the position/size of grid items, and on to "freeze" them in the place/size they left them.
Current (wrong) behavior:
My problem is when I am in non-static mode, I drag the grid items around to where I want them, and toggle static mode on, they reset to where they were before. i.e.:
I drag Element A to the bottom of the stack, and when I turn on static mode, it shifts to the middle of the stack, and turning it back off returns Element A to the top of the stack. Element A will then loop between the middle and top, depending on whether static mode is on or off.
Here is an example of what I mean:
What I have tried:
toggleStaticMode
function to provide more detail. I made the isStatic
state, and set the new value of it every click.static
value for each object to stay as true or false to note changes. When object a
and c
's static
value was true and b
's was isStatic
, toggling static mode changed the position of b
, as well as a
and c
. The values printed in the console said they did not change.None of the above showed me what was happening or why it was. The docs also did not provide much information about this.
One "hackish" solution that may work is to use local storage/cookies from this example, but I do not want to do this every time a user toggles static mode. This solution might not actually fix this problem.
This may be a bug, but I would like to find a better work-around if possible.
Full code used:
import React, {
useState
} from "react";
import GridLayout from "react-grid-layout";
export default function Home() {
let [layout, setLayout] = useState([{
i: "a",
x: 1,
y: 0,
w: 8,
h: 4,
static: false
},
{
i: "b",
x: 3,
y: 2,
w: 3,
h: 2,
static: false
},
{
i: "c",
x: 5,
y: 4,
w: 1,
h: 2,
static: false
},
]);
function printLayoutItem(val) {
console.log(layout[val].i + ": (w: " + layout[val].w + " h: " + layout[val].h + " x: " + layout[val].x + " y: " + layout[val].y + ")");
}
function toggleStaticMode() {
setLayout(
layout.map((l) => {
return { ...l,
static: !l.static
};
})
);
}
// let [isStatic, setStatic] = useState(false);
// function toggleStaticMode() {
// if (isStatic) setStatic(false)
// else setStatic(true);
// printLayoutItem(0);
// printLayoutItem(1);
// printLayoutItem(2);
// setLayout([
// { i: "a", x: 3, y: 0, w: 8, h: 4, static: isStatic },
// { i: "b", x: 0, y: 2, w: 3, h: 2, static: isStatic},
// { i: "c", x: 5, y: 4, w: 1, h: 2, static: isStatic },
// ]);
// }
return ( <div>
<main>{/* thanks for this beautiful Tidy, StackOverflow */}
<button onClick ={toggleStaticMode}>
Toggle Static Mode
</button>
<GridLayout
className="layout"
layout={layout}
cols={4}
rowHeight={100}
width={1200}>
<div key="a">Hello A </div>
<div key="b">Hello B </div>
<div key="c">Hello C </div>
</GridLayout >
</main>
</div>
);
}
<!-- Snippet does not run correctly, please see the example below for a working example of the error occuring -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I also uploaded this code to CodeSandbox to show a full, working example of my current problem.
This example is different in the way that it makes use of react classes/states and not useState
, but the same problem occurs. This example shows the size/position values changing when toggling static mode.
EDIT:
Someone Special's answer got me in the right direction, however, when the columns rearrange to a smaller value than the minimum size, the items scatter upon toggle again. An example of this can be viewed here.
Updated: Found your problem. You updated the lg array but u did not update the sm array.
toggleStaticMode() {
this.setState({
layouts: {
lg: this.state.layouts.lg.map((l) => {
return { ...l, static: !l.static };
}),
sm: this.state.layouts.sm.map((l) => {
return { ...l, static: !l.static };
})
}
});
}
By updating the sm array in togglestaticmode, it works on my side. Sandbox
Previously:
I haven't worked with this react-grid-layout before, however I see your onLayout function as follows.
onLayoutChange(layout, layouts) {
this.props.onLayoutChange(layout, layouts);
}
You update your layout using the props, and you set the initialprops, but that only sets the initialProps - it doesn't listen to changes to props from your parent component.
Meaning, when you use props.onLayoutChange (from your parent component), your parent changes, but your component state did not change.
You can either,