-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
improves new react demo example #2162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07f4f15
97c5638
1031f28
744e801
d4692b0
59deeb8
72aaab8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,9 +33,10 @@ <h2>Controlled stack</h2> | |
// | ||
// Controlled example | ||
// | ||
const ControlledStack = ({ items, addItem, removeItem, id }) => { | ||
const ControlledStack = ({ items, addItem, removeItem, changeItems }) => { | ||
const refs = useRef({}) | ||
const gridRef = useRef() | ||
const gridContainerRef = useRef(null) | ||
refs.current = {} | ||
|
||
if (Object.keys(refs.current).length !== items.length) { | ||
|
@@ -49,13 +50,13 @@ <h2>Controlled stack</h2> | |
// no need to init twice (would will return same grid) or register dup events | ||
const grid = gridRef.current = GridStack.init( | ||
{ | ||
float: true, | ||
float: false, | ||
acceptWidgets: true, | ||
disableOneColumnMode: true, // side-by-side and fever columns to fit smaller screens | ||
column: 6, | ||
minRow: 1, | ||
}, | ||
`.controlled-${id}` | ||
gridContainerRef.current | ||
) | ||
.on('added', (ev, gsItems) => { | ||
if (grid._ignoreCB) return; | ||
|
@@ -68,45 +69,35 @@ <h2>Controlled stack</h2> | |
}); | ||
}) | ||
.on('removed', (ev, gsItems) => { | ||
/* Looks like a bug in GridStack */ | ||
const dirtyNodes = grid.engine.getDirtyNodes(); | ||
if (dirtyNodes !== undefined && dirtyNodes.length !== 0) { | ||
const newItems = grid.save(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that is not the best way to do this... it's going to set a new list and REBUILD EACH widget all over again when only a few might have changed AND really only the x,y,w,h might have changed so the structure values need to change but not trigger a react rebuild because the items are already at the correct location. Only client side structure needs syncing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean creating a list with new objects will force GridStack to rebuild each widget or React? Because as far as I know it is not the problem for React since the ids that are used as keys have not been changed, hence React will not remount elements. Please correct me if i am wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did you actually debug react and made sure no new elements were created ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I did. |
||
changeItems(newItems); | ||
} | ||
|
||
if (grid._ignoreCB) return; | ||
gsItems.forEach(n => removeItem(n.id)); | ||
}) | ||
|
||
.on('change', (ev, gsItems) => { | ||
const newItems = grid.save(false); | ||
changeItems(newItems); | ||
}) | ||
} else { | ||
// | ||
// update existing grid layout, which is optimized to updates only diffs (will add new/delete items for examples) | ||
// | ||
const grid = gridRef.current; | ||
const layout = []; | ||
items.forEach((a) => layout.push( | ||
const layout = items.map((a) => | ||
refs.current[a.id].current.gridstackNode || {...a, el: refs.current[a.id].current} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding this line, i see that you have added el property to each item, I also had to do so when i was writing a React wrapper component to avoid creating a new widget instead of updating. And i had to ts-ignore it, since i got typescript error, because the This problem forced me to dig into the sources of GridStack There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, the problem is the id YOU provide is not guaranteed to be there, nor unique, so the lib cannot depend on it being there... that's why I have _id internally to know what they are. so el is used (if present, else creates a new item). if you look at the Angular wrapper, I get the gridstackNode (which has _id and .el) to load, else it uses widget for new items positions... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I shoudl document that better... but again have 2 list is the wrong approach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have made my own react wrapper component over gridstack with a custom hook which can make any component draggable and GridStackDraggable component to make any react component gridstack draggable. Do you wish to have a look on it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that would be great - if you could post something similar to the Angular component I created, that would help others as I don't use React. Then peolple can interate and improved on it. |
||
)); | ||
); | ||
grid._ignoreCB = true; // hack: ignore added/removed since we're the one doing the update | ||
grid.load(layout); | ||
delete grid._ignoreCB; | ||
} | ||
|
||
// NOTE: old code is incorrect as it re-does the GS binding, but dragged item is left behind so you get dup DOM elements with same ids | ||
// grid.batchUpdate() | ||
// items.forEach((a) => { | ||
// // remove existing widgets | ||
// if (refs.current[a.id] && refs.current[a.id].current) { | ||
// grid.removeWidget(refs.current[a.id].current, false, false) | ||
// } | ||
// grid.makeWidget(refs.current[a.id].current) | ||
// }) | ||
// grid.batchUpdate(false) | ||
|
||
}, [items]) | ||
|
||
useEffect(() => { | ||
return () => { | ||
// console.log('cleanup', id) | ||
// gridRef.current.destroy(false, false) | ||
// gridRef.current = null | ||
} | ||
}) | ||
|
||
return ( | ||
// ******************** | ||
// NOTE: constructing DOM grid items in template when gridstack is also allowed editing (dragging between grids, or adding/removing from say a toolbar) | ||
|
@@ -119,10 +110,10 @@ <h2>Controlled stack</h2> | |
// is not robust as things get added, and pollutes the DOM attr for default/missing entries, vs optimized code in GS. | ||
// ******************** | ||
<div style={{ width: '100%', marginRight: '10px' }}> | ||
<div className={`grid-stack controlled-${id}`}> | ||
<div className="grid-stack" ref={gridContainerRef}> | ||
{items.map((item, i) => { | ||
return ( | ||
<div ref={refs.current[item.id]} key={`${id}-${item.id}`} className={'grid-stack-item'} gs-id={item.id} gs-w={item.w} gs-h={item.h} gs-x={item.x} gs-y={item.y}> | ||
<div ref={refs.current[item.id]} key={item.id} className="grid-stack-item" gs-id={item.id} gs-w={item.w} gs-h={item.h} gs-x={item.x} gs-y={item.y}> | ||
<div className="grid-stack-item-content"> | ||
<Item {...item} /> | ||
</div> | ||
|
@@ -142,32 +133,42 @@ <h2>Controlled stack</h2> | |
const [items2, setItems2] = useState([{ id: 'item-2-1', x: 0, y: 0, w: 1, h: 1 }, { id: 'item-2-2', x: 0, y: 1, w: 1, h: 1 }, { id: 'item-2-3', x: 1, y: 0, w: 1, h: 1 }]) | ||
|
||
return ( | ||
<div style={{display: 'flex'}}> | ||
<div style={{ display: 'flex', width: '50%' }}> | ||
<ControlledStack | ||
id='gs1' | ||
items={items1} | ||
addItem={(item) => { | ||
setItems1(items => [...items, item]) | ||
}} | ||
removeItem={(id) => { | ||
setItems1(items => items.filter(i => i.id !== id)) | ||
}} | ||
/> | ||
</div > | ||
<div style={{ display: 'flex', width: '50%' }}> | ||
<ControlledStack | ||
id='gs2' | ||
items={items2} | ||
addItem={(item) => { | ||
setItems2(items => [...items, item]) | ||
}} | ||
removeItem={(id) => { | ||
setItems2(items => items.filter(i => i.id !== id)) | ||
}} | ||
/> | ||
<div> | ||
<div style={{display: 'flex', gap: '16px', marginBottom: '16px'}}> | ||
<div></div> | ||
</div> | ||
|
||
<div style={{ display: 'flex', gap: '16px', marginBottom: '16px' }}> | ||
<button onClick={() => setItems1(items => [...items, { id: `item-1-${Date.now()}`, x: 2, y: 0, w: 2, h: 2 }])}>Add Item to 1 grid</button> | ||
<button onClick={() => setItems2(items => [...items, { id: `item-2-${Date.now()}`, x: 2, y: 0, w: 2, h: 2 }])}>Add Item to 2 grid</button> | ||
</div> | ||
</div > | ||
<div style={{display: 'flex'}}> | ||
<div style={{ display: 'flex', width: '50%' }}> | ||
<ControlledStack | ||
items={items1} | ||
addItem={(item) => { | ||
setItems1(items => [...items, item]) | ||
}} | ||
removeItem={(id) => { | ||
setItems1(items => items.filter(i => i.id !== id)) | ||
}} | ||
changeItems={(items) => setItems1(items)} | ||
ruslauz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
</div > | ||
<div style={{ display: 'flex', width: '50%' }}> | ||
<ControlledStack | ||
items={items2} | ||
addItem={(item) => { | ||
setItems2(items => [...items, item]) | ||
}} | ||
removeItem={(id) => { | ||
setItems2(items => items.filter(i => i.id !== id)) | ||
}} | ||
changeItems={(items) => setItems2(items)} | ||
/> | ||
</div> | ||
</div > | ||
</div> | ||
) | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean ? 'removed' should only be called on item being removed | added, and change on existing ones changing values. all 3 cases need to update your list - which is why I don't like having 2 list to sync and why this is not the best way...
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one case when you move one widget from one grid into another and when the widget is removed form the original grid, a relayout occurs but the change event is not triggered despite the fact that some widgets changed their position, so i have to manually check this and call change handler to have the relevant layout
I can try to make a video that will explain what i mean if you wish
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed it #2163. thanks !