dnd-kit
dnd-kit copied to clipboard
active.data.current gets lost "on drag end".
I have a draggable (also using an DragOverlay
) defined as such:
const { attributes, listeners, setNodeRef } = useDraggable({
id: `${type}_${id}`,
data: {
id,
type,
},
});
and I handle the onDragStart
and onDragEnd
where my DnDContext
is rendering. If I console log active.data.current
inside onDragStart
, I can see the data I provided. Unfortunately, once I let go of the mouse and the drop should happen, active.data.current
is just an empty object.
Using the latest version, I have no idea how to even replicate this bug or why this would ever happen. Any ideas?
Digging a little deeper, I see: https://github.com/clauderic/dnd-kit/blob/919f21af9382b88141b6305769836960d5844fa5/packages/core/src/components/DndContext/DndContext.tsx#L165 - "// It's possible for the active node to unmount while dragging". This is the only thing I see that would mess up with the data object and, it's true, the container unmounts if I drag outside of it.
Is there any way to fix this gracefully or do I have to "suspend" my state somehow such that the item remains mounted until I finish my drag?
Same here.
I'm also experiencing active.data.current
getting lost, but in the collisionDetection
function. However, it seems to me that onDragStart
and collisionDetection
use the same active
object (let me know if I'm wrong).
The version of @dnd-kit/core
that I'm using is 6.0.3
.
Update: I just came across this. I'll do some more digging and create a separate issue if necessary!
@wiltsu @shawnshuang Assuming it is not a bug, as I noted, the only way for active.data.current
to be missing is if the original draggable gets unmounted.
If you have something complex, you can just run a console.log
on unmount to see if your draggable's really unmounting:
useEffect(() => {
return () => {
console.log('Unmounted.');
}
}
I'm experiencing the same issue within a virtualized list when dragging the item far enough (meaning that the virtualization unmounts the original instance).
It seems a bug to me as the data
information should be preserved on the active object, just like the id is preserved.
The issue is still open, but by any chance, have you found a workaround?
I use a React context to keep track on the tracked entity. useDraggable exposes isDragging. If this is true, I set the props of the component I want to drag in the context.
The DraggableOverlay renders the same (or similar) component as representation for the dragged one, if active from useDndContext is truthy, and this component uses the data of the context for its props. If active is falsy, I don't render anything, but one could also set the context value to null.
An example context could look like that (might have typos, I am in a hurry):
import React, { createContext, useContext, useEffect, useState } from "react"
import type { PropsType } from "../component"
type DraggedPropsContextType = {
draggedProps: PropsType | null
setDraggedProps: ( props: PropsType | null ) => void
}
const DraggedPropsContext = createContext<DraggedPropsContextType | undefined>( undefined )
export function useDraggedProps () {
const context = useContext( DraggedPropsContext )
if ( !context ) {
throw new Error( "useDraggedProps must be used within a DraggedPropsProvider" )
}
return context
}
export function DraggedPropsProvider ( { children }: { children: React.ReactNode } ) {
const [ draggedProps, setDraggedProps ] = useState<PropsType | null>( null )
return (
<DraggedPropsContext.Provider value={ { draggedProps, setDraggedProps } }>
{ children }
</DraggedPropsContext.Provider>
)
}
And the DraggableOverlay could be done like:
export default function DraggableSupport ( { children }: {children: React.ReactNode}) {
return (
<DraggedPropsProvider>
<DndContext collisionDetection={ pointerWithin }>
{ children }
<DraggableOverlayInner />
</DndContext>
</DraggedPropsProvider>
)
}
function DraggableOverlayInner () {
const { active } = useDndContext();
return React.createPortal(
<DragOverlay>
{ active && (
<DraggableOverlayComponent />
) }
</DragOverlay>,
document.body
);
}
function DraggableOverlayComponent () {
const { draggedProps } = useDraggedProps() // from the DraggedProps context
return (
<>
{ draggedProps && <DraggableComponent { ...draggedProps } /> }
</>
)
}