React
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.7/underscore-min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gridstack@10.3.1/dist/gridstack.min.css">
<script src="https://cdn.jsdelivr.net/npm/gridstack@10.3.1/dist/gridstack-all.js"></script>
<div id="root"></div>
CSS
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
.grid-stack-item-content {
background: #18bc9c;
display: flex;
align-content: center;
justify-content: center;
text-align: center;
}
React
function MyGridThing({
items, // Map[ID, { x, y, w, h }]
onChangeItems, // (update: Endo[Map[ID, { x, y, w, h }]]) => void
children, // ID => JSX
}) {
// Map[Id, Ref<Elem>]
const itemRefs = React.useMemo(() => _.mapObject(items, () => React.createRef()), [items])
// keep a Ref around the latest `onChangeItems` function
const onChangeItemsRef = React.useRef(onChangeItems)
onChangeItemsRef.current = onChangeItems
// sync the `items` to a GridStack that we keep in `gridRef`
const gridRef = React.useRef()
const gridContainerRef = React.useRef()
React.useEffect(() => {
console.log('in MyGridThing useEffect to sync items to grid')
const grid =
gridRef.current ||
(gridRef.current = GridStack.init(
{ float: false, animate: false, margin: 5 },
gridContainerRef.current,
).on('change', (e, items) => {
console.log('onChange event!')
onChangeAdapter(onChangeItemsRef.current, items)
}))
grid.batchUpdate()
grid.removeAll(false)
for (const id in items) {
const elem = itemRefs[id].current
if (elem) {
grid.makeWidget(elem, { id, ...items[id] })
} else {
console.info('missing ref for item', id)
}
}
grid.batchUpdate(false)
grid.setAnimation(true)
}, [items, itemRefs])
return (
<div className="grid-stack controlled" ref={gridContainerRef}>
{_.map(items, (item, id) => {
return (
<div ref={itemRefs[id]} key={id} className="grid-stack-item">
<div className="grid-stack-item-content">{children(item, id, items)}</div>
</div>
)
})}
</div>
)
}
function onChangeAdapter(onChangeReceiver, changedItems) {
onChangeReceiver(oldItems => {
const newItems = { ...oldItems }
changedItems.forEach(({ id, x, y, w, h }) => {
const oldItem = oldItems[id] || {}
let changed = false
if (oldItem.x != x || oldItem.y != y) {
changed = true
console.log('item', id, 'moved to', { x, y })
}
if (oldItem.w != w || oldItem.h != h) {
changed = true
console.log('item',...