Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node
Describe the bug
I'm creating a DND library on top of dnd-kit/dom and have run into this issue. By nature, sorting libraries use optimistic updates in the DOM tree to perform updates on the fly. Once complete, I need to sync the current DOM order with the order in my data. Sometimes this works well, but other times Solid tries to sort nodes that were already sorted by the optimistic DND update. In some cases, it even throws the error:
Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
Your Example Website or App
Unexpected move: https://playground.solidjs.com/anonymous/759847e3-477a-4a02-bd70-48d05d87a87e
Throw: https://playground.solidjs.com/anonymous/4d15e0bf-5bbb-4784-bc77-b96000ccd884
Steps to Reproduce the Bug or Issue
Render the list of 5 elements: createSignal([1, 2, 3, 4, 5])
Move element 1 after 4 in the DOM.
Sync the order in the data: setItems([2, 3, 4, 1, 5])
→ Solid will reorder the items, so instead of the expected 2 3 4 1 5, you'll get 3 4 1 2 5.
Render the list of 5 elements: createSignal([1, 2, 3, 4, 5])
Move element 3 after 5 in the DOM.
Sync the order in the data: setItems([1, 2, 4, 5, 3])
→ Solid will throw an error, and you'll get 1 2 5 4.
Expected behavior
I fully understand the nature of SolidJS and why this happens, but it would be good to have some way to force the correct order of items in For without completely rerendering the whole list. Or at least, not throw an error in the second case. I’ve tried using For/Index and Keyed from solid-primitives, but nothing helps. The only two ways I've found to solve this are: restoring the original order of DOM items before calling setItems, which is tricky and unreliable; or completely rerendering the whole list, which is slow and also breaks transitions for the DND library. I also tried to find a way to memoize DOM nodes after rendering to restore them when rerendering the list, but I didn’t succeed. Maybe someone knows a way to do this.
Screenshots or Videos
No response
Platform
- OS: -
- Browser: Any
- Version: ^1.9
Additional context
No response
Related but closed (not solved) issue https://github.com/solidjs/solid/issues/1898
This is the issue https://github.com/ryansolid/dom-expressions/pull/388
Basically what's happening is that solid takes some shortcuts (for performance reasons [albeit this can be fixed without losing performance]).
To illustrate it, instead of taking the first node in the list, it does something on the lines of parent.firstChild, assuming the first child is still the first item in the list. So if you happen to move nodes around, then the assumption that the first node is the first item in the list is broken and well.
This code was written ages ago, while the linked issue seems a bit stalled its highly considered, and hopefully we can fix this.
The thing is moving the items back to where they were is how all DnD has worked to my knowledge with declarative frameworks from the beginning of time. Or basically you don't actually drop the element, you just capture that it would be dropped and then update the data that would make it re-render properly. This is how it has worked in every framework forever. We get this issue from time to time and I generally point people to they are doing things wrong.
I am not convinced that the elements can be made out of order and we won't hit an issue sometime in the future because the framework assumes it knows where everything is. It seems like an invitation to endless edge cases. Obviously if someone was to solve this in a performant way it would be huge, but I'd be cautious getting ones hopes up.