dnd-kit icon indicating copy to clipboard operation
dnd-kit copied to clipboard

active.data.current gets lost "on drag end".

Open growthwp opened this issue 2 years ago • 9 comments

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?

growthwp avatar Jun 11 '22 07:06 growthwp

Same here.

wiltsu avatar Jun 22 '22 09:06 wiltsu

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!

shawnshuang avatar Jul 06 '22 20:07 shawnshuang

@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.');
  }
}

growthwp avatar Jul 07 '22 14:07 growthwp

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.

Fabianopb avatar Jul 29 '22 08:07 Fabianopb

The issue is still open, but by any chance, have you found a workaround?

RemyMachado avatar Nov 14 '22 17:11 RemyMachado

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 } /> }
			</>
	)
}

marcus-wishes avatar Jan 08 '23 10:01 marcus-wishes