---
title: useDragAndDrop
description: API reference for the useDragAndDrop hook in Strapi's Content Manager
tags:
  - content-manager
  - hooks
  - drag-and-drop
---

An abstraction around `react-dnd`'s `useDrag` and `useDrop` hooks. It provides a simple API to handle drag and drop
events maintaining the same behaviour across the application e.g.

1. When we consider the item to be above a new drop zone and move it immediately.
2. Item is above the drop zone, we move it when user drops it.

This hook also wraps an internal hook `useKeyboardDragAndDrop` which implements keyboard accessibile drag and drop by
returning an onKeyDown handler to be passed to the component's drag icon button.

## Usage

:::note
The following examples assume that you have already set up the `DndProvider` with `HTML5Backend` in your application and
that you are somewhat familiar with `@strapi/design-system` components.
:::

### Basic usage

Move items on hovering over drop zone

Below is a basic example usage where we move items immediately and we're not interested in rendering custom previews in the DragLayer.
However, we do replace the current item with a placeholder.

```jsx
import { Box, Flex, IconButton } from '@strapi/design-system';
import { Drag } from '@strapi/icons';

import { useDragAndDrop } from 'path/to/hooks';
import { composeRefs } from 'path/to/utils';

import { Placeholder } from './Placeholder';

const MyComponent = ({ onMoveItem }) => {
  const [{ handlerId, isDragging, handleKeyDown }, myRef, dropRef, dragRef] = useDragAndDrop(true, {
    type: 'my-type',
    index,
    onMoveItem,
  });

  const composedRefs = composeRefs(myRef, dragRef);

  return (
    <Box ref={dropRef} cursor={'all-scroll'}>
      {isDragging ? (
        <Placeholder />
      ) : (
        <Flex ref={composedRefs} data-handler-id={handlerId}>
          <IconButton
            tag="div"
            role="button"
            tabIndex={0}
            aria-label="Drag"
            borderWidth={0}
            onKeyDown={handleKeyDown}
          >
            <Drag />
          </IconButton>
          {'My item'}
        </Flex>
      )}
    </Box>
  );
};
```

### Basic usage - Move items on dropping over drop zone

Below is a basic example usage where we move items immediately without custom previews in the DragLayer. We do not replace
the current item until its dropped and show a drop placeholder instead next to drop target using drag direction.

```jsx
import { Box, Flex, IconButton } from '@strapi/design-system';
import { Drag } from '@strapi/icons';

import { useDragAndDrop } from 'path/to/hooks';
import { composeRefs } from 'path/to/utils';

import { Placeholder } from './Placeholder';

const MyComponent = ({ onDropItem }) => {
  const [{ handlerId, handleKeyDown, isOverDropTarget, direction }, myRef, dropRef, dragRef] =
    useDragAndDrop(true, {
      type: 'my-type',
      index,
      onDropItem,
    });

  const composedRefs = composeRefs(myRef, dragRef);

  return (
    <Box ref={dropRef} cursor={'all-scroll'}>
      {isOverDropTarget && <Placeholder direction={direction} />}
      <Flex ref={composedRefs} data-handler-id={handlerId}>
        <IconButton
          tag="div"
          role="button"
          tabIndex={0}
          aria-label="Drag"
          borderWidth={0}
          onKeyDown={handleKeyDown}
        >
          <Drag />
        </IconButton>
        {'My item'}
      </Flex>
    </Box>
  );
};
```

### Using custom previews

The only really difference between the previous example and this one is that
we're using the `getEmptyImage` function from `react-dnd-html5-backend` to render custom previews in the DragLayer.

```jsx
import { getEmptyImage } from 'react-dnd-html5-backend';
import { Box, Flex, IconButton } from '@strapi/design-system';
import { Drag } from '@strapi/icons';

import { useDragAndDrop } from 'path/to/hooks';
import { composeRefs } from 'path/to/utils';

import { Placeholder } from './Placeholder';

const MyComponent = ({ onMoveItem }) => {
  const [{ handlerId, isDragging, handleKeyDown }, myRef, dropRef, dragRef, dragPreviewRef] =
    useDragAndDrop(true, {
      type: 'my-type',
      index,
      onMoveItem,
    });

  // highlight-start
  useEffect(() => {
    dragPreviewRef(getEmptyImage());
  }, [dragPreviewRef]);
  // highlight-end

  const composedRefs = composeRefs(myRef, dragRef);

  return (
    <Box ref={dropRef} cursor={'all-scroll'}>
      {isDragging ? (
        <Placeholder />
      ) : (
        <Flex ref={composedRefs} data-handler-id={handlerId}>
          <IconButton
            tag="div"
            role="button"
            tabIndex={0}
            aria-label="Drag"
            borderWidth={0}
            onKeyDown={handleKeyDown}
          >
            <Drag />
          </IconButton>
          {'My item'}
        </Flex>
      )}
    </Box>
  );
};
```

## Typescript

```ts
import { Identifier } from 'dnd-core';
import { ConnectDropTarget, ConnectDragSource, ConnectDragPreview } from 'react-dnd';

interface UseDragAndDropOptions {
  index: number | Array<number>;
  onMoveItem?: (newIndex: number | Array<number>, currentIndex: number | Array<number>) => void;
  /**
   * @default "regular"
   * Defines whether the change in index should be immediately over another
   * dropzone or half way over it (regular).
   */
  dropSensitivity?: 'immediate' | 'regular';
  item?: object;
  /**
   * @default 'STRAPI_DND'
   */
  type?: string;
  onCancel?: (index: number) => void;
  onDropItem?: (currentIndex: number | Array<number>, newIndex: number | Array<number>) => void;
  onEnd?: () => void;
  onGrabItem?: (index: number) => void;
  onStart?: () => void;
}

type UseDragAndDropReturn = [
  props: {
    handlerId: Identifier;
    isDragging: boolean;
    handleKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
    isOverDropTarget: boolean;
    direction: string;
  },
  objectRef: React.RefObject<HTMLElement>,
  dropRef: ConnectDropTarget,
  dragRef: ConnectDragSource,
  dragPreviewRef: ConnectDragPreview,
];

type UseDragAndDrop = (active: boolean, options: UseDragAndDropOptions) => UseDragAndDropReturn;
```

## Accessibility

Its advised to implement a [live text region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) in the
parent component holding your individual dnd children. This should be done to inform the user of the current state of the drag and drop.
To implement this, you need to pass the `onDropItem`, `onGrabItem` and `onCancel` callbacks to the `useDragAndDrop` hook which are fired
only with the purpose of updating the live region, hence why they're optional. You would also update the live region as part of your
`onMoveItem` callback. There are generic messages that can be used in the `intl` provider, an example of using this may look like:

```js
setLiveText(
  formatMessage(
    {
      id: getTrad('dnd.drop-item'),
      defaultMessage: `{item}, dropped. Final position in list: {position}.`,
    },
    {
      item: 'my item',
      position: 1,
    }
  )
);
```

## Further Reading

- [react-dnd docs](https://react-dnd.github.io/react-dnd/docs/overview)
- [useDrag API](https://react-dnd.github.io/react-dnd/docs/api/use-drag)
- [useDrop API](https://react-dnd.github.io/react-dnd/docs/api/use-drop)
- [useDragLayer API](https://react-dnd.github.io/react-dnd/docs/api/use-drag-layer)

## Troubleshooting

### Firefox quirks

You might notice in the [basic usage](#basic-usage) section this piece of code:

```jsx
<IconButton
  tag="div"
  role="button"
  tabIndex={0}
  aria-label="Drag"
  borderWidth={0}
  onKeyDown={handleKeyDown}
>
  <Drag />
</IconButton>
```

In `firefox` the drag handler will not work if you click and drag when the element is a `button`, this is known [bug in the browser](https://bugzilla.mozilla.org/show_bug.cgi?id=568313).
Therefore the workaround is to use the `forwardedAs` prop to render a `div` instead of a `button`
and add the `role` and `tabIndex` props to make this accessible. The actual `IconButton` component
adds an accessible lable from the `aria-label` prop. So we don't have to concern ourselves with that.
