React DnD / TypeScript: How to use getDropResult?

Viewed 117

In my React web app, I have a number of react-dnd drop targets:

const DropTarget = () => {
  const [{ canDrop, isOver }, drop] = useDrop(() => ({
    accept: 'MyDnDItemType',
    drop: () => ({ name: 'name of this drop target' })
  }))
   ...
}  

and items to drag around and drop in those targets:

const DraggableItem = () => {
  const [{ isDragging }, drag] = useDrag({
    type: 'MyDnDItemType'
  })
   ...

When a DraggableItem is dropped into one of the DropTargets, I would like to get the drop target name. The react-dnd useDrag docs say in the end(item, monitor) section:

If [...] the drop target specified a drop result by returning a plain object from its drop() method, it will be available as monitor.getDropResult().

However, when I add an end(...) function in useDrag(...) and attempt to access the plain object returned, I get a TypeScript compiler error Object is of type 'unknown'.ts(2571).

const DraggableItem = () => {
  const [{ isDragging }, drag] = useDrag({
    end: (item, monitor) => {
      const dropResult = monitor.getDropResult();
      // console.log('dropResult:', dropResult)
      // sample log output:
      //   dropResult: {dropEffect: 'move', name: 'name of this drop target'}

      // PROBLEM: this fails to compile with
      //   Object is of type 'unknown'.ts(2571)
      console.log('name:', dropResult.name)
    },
    type: 'MyDnDItemType'
  })
   ...

I also tried end: (item, monitor: DragSourceMonitor) ... and, after reading about this somewhat related issue and delving into the source code, tried to use an explicit type:

type MyType = {
  name: string;
};

const DraggableItem = () => {
  const [{ isDragging }, drag] = useDrag({
    end: (item, monitor: DragSourceMonitor<MyType>) => {
     ... (as above) ...

but the error persists.

How can I access the plain object returned by getDropResult() ?

1 Answers

Figured it out: Was on the right track, but didn't follow through.

Some quick background info: Until a couple of minutes ago, I knew only very little about TypeScript and, as for generics, only what I remember about C++ templates from ~20 years ago; probably close to nothing. It's not that hard, though:

The DragSourceMonitor interface takes two type parameters DragObject and DropResult, which per default are unknown:

export interface DragSourceMonitor<DragObject = unknown, DropResult = unknown>

One of the methods on that interface is getDropResult which takes a single type parameter T, using DropResult as default:

getDropResult<T = DropResult>(): T | null

This provides two entry points to fix the compiler error:

Either use own types to pass to DragSourceMonitor, e.g.

type MyDragObject = {
  name: string;
}

type MyDropResult = {
  name: string;
}

const DraggableTreeItem = (props: TreeItemProps) => {

  const [{ isDragging }, drag] = useDrag({
    collect: (monitor: DragSourceMonitor) => ({
      isDragging: monitor.isDragging()
    }),
   ...
  end: (item, monitor: DragSourceMonitor<MyDragObject, MyDropResult>) => {
   ...

or pass the type to use only when calling getDropResult(...), e.g.

type MyDropResult = {
  name: string;
}

const DraggableTreeItem = (props: TreeItemProps) => {

  const [{ isDragging }, drag] = useDrag({
   ...
  end: (item, monitor: DragSourceMonitor) => {
      const dropResult = monitor.getDropResult<MyDropResult>()
      // getDropResult returns `T | null`, so need to test
      // against null to fix `Object is possibly 'null'.ts(2531)`
      if (dropResult) {
        console.log('name:', dropResult.name)
      }
     ...
  }
   ...
Related