Skip to content

Commit

Permalink
Hooks API Refactorings (#1261)
Browse files Browse the repository at this point in the history
* refactor: update dragsource item in hooks api

Because the dragSource item is derivative of props, and props are available when we are using the hook,
the dragSourceItem returned from 'begin' is now the '.item' property of the dragSource spec. Additionally, the type
field has been moved into the dragSource item.
~

* refactor: add item to droptarget api argument list when appropriate

* refactor: dropTarget.type => dropTarget.accept

* fix: some hooks-based examples

* docs: add pages describing hooks API

* refactor: update hooks' knight sample to use portals/ref-Forwarding

* docs: update boxWithImage example to use portals/refforwarding

* refactor: clean up useDrag

* feat: add a useDragPreview hook

* refactor: create dragDrop refs when no ref is configured

* feat: improve auto-ref creatino

* docs: update useDrag/useDrop documentation

* docs: udpate useDrag/useDrop docs
  • Loading branch information
darthtrevino committed Mar 10, 2019
1 parent 78cef8f commit 46b7893
Show file tree
Hide file tree
Showing 48 changed files with 570 additions and 296 deletions.
58 changes: 58 additions & 0 deletions packages/documentation/markdown/docs/05 Hooks-Based API/useDrag.md
@@ -0,0 +1,58 @@
---
path: '/docs/api/use-drag'
title: 'useDrag'
---

## EXPERIMENTAL API - UNSTABLE

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# useDrag

A hook to use the current component as a drag-source.

```js
import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
const {
useDrag,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

function myDraggable(props) {
const collectedProps = useDrag(spec)
}
```

### useDrag Parameters

- **`spec`** Specification object, see below for details on how to construct this

### Return Value

useDrag returns an array:

0. An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
1. The React ref to use. This is automatically created if no `ref` field is defined on the specification object.

### Specification Object Members

- **`ref`**: Required. A ref object to use to attach to the draggable element.

- **`preview`**: Optional. An HTML Element or a ref object attached to the dragPreview element.

- **`previewOptions`**: Optional. A plain JavaScript object describing drag preview options.

- **`item`**: Required. A plain JavaScript object describing the data being dragged. This is the _only_ information available to the drop targets about the drag source so it's important to pick the _minimal_ data they need to know. You may be tempted to put a complex reference here, but you should try very hard to avoid doing this because it couples the drag sources and drop targets. It's a good idea to return something like `{ type, id }` from this method.

- **`item.type`**: Required. Either a string, an ES6 symbol`. Only the [drop targets](/docs/api/drop-target) registered for the same type will react to the items produced by this drag source. Read the [overview](/docs/overview) to learn more about the items and types.

- **`options`**: Optional. A plain object. If some of the props to your component are not scalar (that is, are not primitive values or functions), specifying a custom`arePropsEqual(props, otherProps)`function inside the`options` object can improve the performance. Unless you have performance problems, don't worry about it.

- **`begin(monitor)`**: Optionaln. Fired when a drag operation begins.

- **`end(monitor)`**: Optional. When the dragging stops, `end` is called. For every `begin` call, a corresponding `end` call is guaranteed. You may call `monitor.didDrop()` to check whether or not the drop was handled by a compatible drop target. If it was handled, and the drop target specified a _drop result_ by returning a plain object from its `drop()` method, it will be available as `monitor.getDropResult()`. This method is a good place to fire a Flux action. _Note: If the component is unmounted while dragging, `component` parameter is set to be `null`._

- **`canDrag(monitor)`**: Optional. Use it to specify whether the dragging is currently allowed. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dragging based on some predicate over `props`. _Note: You may not call `monitor.canDrag()` inside this method._

- **`isDragging(monitor)`**: Optional. By default, only the drag source that initiated the drag operation is considered to be dragging. You can override this behavior by defining a custom `isDragging` method. It might return something like `props.id === monitor.getItem().id`. Do this if the original component may be unmounted during the dragging and later “resurrected” with a different parent. For example, when moving a card across the lists in a Kanban board, you want it to retain the dragged appearance—even though technically, the component gets unmounted and a different one gets mounted every time you move it to another list. _Note: You may not call `monitor.isDragging()` inside this method._

* **`collect`**: Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, `monitor` and `props`. Read the [overview](/docs/overview) for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
@@ -0,0 +1,32 @@
---
path: '/docs/api/use-drag-layer'
title: 'useDrag'
---

## EXPERIMENTAL API - UNSTABLE

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# useDragLayer

A hook to use the current component as a drag-layer.

```js
import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
const {
useDragLayer,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

function myDragLayer(props) {
const collectedProps = useDragLayer(spec)
...
}
```

### useDragLayer Parameters

- **`collect`**: Required. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, `monitor` and `props`. Read the [overview](/docs/overview) for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.

### Return Value

useDragLayer returns an object of collected properties from the collect function.
@@ -0,0 +1,45 @@
---
path: '/docs/api/use-drag-preview'
title: 'useDragPreview'
---

## EXPERIMENTAL API - UNSTABLE

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# useDragPreview

A hook to use the current component as a drag-layer.

```js
import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
const {
useDragPreview,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

function myDragLayer(props) {
const [preview, DragPreview] = useDragPreview(spec)
const collectedProps = useDrag({
...
preview
})

return (
<>
<DragPreview/>
<... rest of item... />
</>
)
}
```

### useDragPreview Parameters

- **`dragPreview`** A refForwarding component that will render the drag preview.

### Return Value

useDragPreview returns an array of two items:

0. The drag preview ref object. This should be passed into useDrag's specification
1. A component to render the dragPreview in your render method.
50 changes: 50 additions & 0 deletions packages/documentation/markdown/docs/05 Hooks-Based API/useDrop.md
@@ -0,0 +1,50 @@
---
path: '/docs/api/use-drop'
title: 'useDrop'
---

## EXPERIMENTAL API - UNSTABLE

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# useDrop

A hook to use the current component as a drop target.

```js
import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
const {
useDrop,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

function myDropTarget(props) {
const collectedProps = useDrop(spec)
}
```

### useDrop Parameters

- **`spec`** Specification object, see below for details on how to construct this

### Return Value

useDrop returns an array:

0. An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
1. The React ref to use. This is automatically created if no `ref` field is defined on the specification object.

### Specification Object Members

- **`ref`**: Required. A ref object to use to attach to the draggable element.

* **`accept`**: Required. A string, an ES6 symbol, an array of either, or a function that returns either of those, given component's `props`. This drop target will only react to the items produced by the [drag sources](/docs/api/drag-source) of the specified type or types. Read the [overview](/docs/overview) to learn more about the items and types.

- **`options`**: Optional. A plain object. If some of the props to your component are not scalar (that is, are not primitive values or functions), specifying a custom `arePropsEqual(props, otherProps)` function inside the `options` object can improve the performance. Unless you have performance problems, don't worry about it.

- **`drop(item, monitor)`**: Optional. Called when a compatible item is dropped on the target. You may either return undefined, or a plain object. If you return an object, it is going to become _the drop result_ and will be available to the drag source in its `endDrag` method as `monitor.getDropResult()`. This is useful in case you want to perform different actions depending on which target received the drop. If you have nested drop targets, you can test whether a nested target has already handled `drop` by checking `monitor.didDrop()` and `monitor.getDropResult()`. Both this method and the source's `endDrag` method are good places to fire Flux actions. This method will not be called if `canDrop()` is defined and returns `false`.

- **`hover(item, monitor)`**: Optional. Called when an item is hovered over the component. You can check `monitor.isOver({ shallow: true })` to test whether the hover happens over _just_ the current target, or over a nested one. Unlike `drop()`, this method will be called even if `canDrop()` is defined and returns `false`. You can check `monitor.canDrop()` to test whether this is the case.

- **`canDrop(item, monitor)`**: Optional. Use it to specify whether the drop target is able to accept the item. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dropping based on some predicate over `props` or `monitor.getItem()`. _Note: You may not call `monitor.canDrop()` inside this method._

* **`collect`**: Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, `monitor` and `props`. Read the [overview](/docs/overview) for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
21 changes: 21 additions & 0 deletions packages/documentation/src/constants.ts
Expand Up @@ -70,6 +70,27 @@ export const APIPages: PageGroup[] = [
},
},
},
{
title: 'Hooks-Based API',
pages: {
USE_DRAG: {
location: '/docs/api/use-drag',
title: 'useDrag',
},
USE_DRAG_LAYER: {
location: '/docs/api/use-drag-layer',
title: 'useDragLayer',
},
USE_DRAG_PREVIEW: {
location: '/docs/api/use-drag-preview',
title: 'useDragPreview',
},
USE_DROP: {
location: '/docs/api/use-drop',
title: 'useDrop',
},
},
},
{
title: 'Connecting to DOM',
pages: {
Expand Down
6 changes: 2 additions & 4 deletions packages/examples-hooks/src/00 Chessboard/BoardSquare.tsx
Expand Up @@ -17,10 +17,8 @@ export interface BoardSquareProps {
export const BoardSquare: React.FC<BoardSquareProps> = (
props: BoardSquareProps,
) => {
const ref = React.useRef(null)
const { isOver, canDrop } = useDrop({
ref,
type: ItemTypes.KNIGHT,
const [{ isOver, canDrop }, ref] = useDrop({
accept: ItemTypes.KNIGHT,
canDrop: () => canMoveKnight(props.x, props.y),
drop: () => moveKnight(props.x, props.y),
collect: mon => ({
Expand Down
49 changes: 26 additions & 23 deletions packages/examples-hooks/src/00 Chessboard/Knight.tsx
Expand Up @@ -5,6 +5,7 @@ import knightImage from './knightImage'

const {
useDrag,
useDragPreview,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

const knightStyle: React.CSSProperties = {
Expand All @@ -13,36 +14,38 @@ const knightStyle: React.CSSProperties = {
cursor: 'move',
}

function createKnightImage() {
if (typeof Image === 'undefined') {
return undefined
}
const img = new Image()
img.src = knightImage
return img
}
const KnightDragPreview = React.forwardRef(
(props, ref: React.Ref<HTMLImageElement>) => {
if (typeof Image === 'undefined') {
return null
}
return <img ref={ref} src={knightImage} />
},
)

export const Knight: React.FC = () => {
const ref = React.useRef(null)
const dragPreview = React.useMemo(createKnightImage, [])
const { isDragging } = useDrag({
ref,
type: ItemTypes.KNIGHT,
preview: dragPreview,
const item = { type: ItemTypes.KNIGHT }
const [DragPreview, preview] = useDragPreview(KnightDragPreview)
const [{ isDragging }, ref] = useDrag({
item,
preview,
collect: mon => ({
isDragging: !!mon.isDragging(),
}),
})

return (
<div
ref={ref}
style={{
...knightStyle,
opacity: isDragging ? 0.5 : 1,
}}
>
</div>
<>
<DragPreview />
<div
ref={ref}
style={{
...knightStyle,
opacity: isDragging ? 0.5 : 1,
}}
>
</div>
</>
)
}
20 changes: 11 additions & 9 deletions packages/examples-hooks/src/01 Dustbin/Copy or Move/Box.tsx
@@ -1,5 +1,6 @@
import * as React from 'react'
import ItemTypes from '../Single Target/ItemTypes'

import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
const {
useDrag,
Expand All @@ -18,16 +19,17 @@ export interface BoxProps {
name: string
}

const Box: React.FC<BoxProps> = ({ name }) => {
const ref = React.useRef(null)
const { opacity } = useDrag({
ref,
type: ItemTypes.BOX,
begin: () => ({ name }),
end(monitor) {
const item = monitor.getItem()
const dropResult = monitor.getDropResult()
interface DropResult {
allowedDropEffect: string
dropEffect: string
name: string
}

const Box: React.FC<BoxProps> = ({ name }) => {
const item = { name, type: ItemTypes.BOX }
const [{ opacity }, ref] = useDrag({
item,
end(dropResult?: DropResult) {
if (dropResult) {
let alertMessage = ''
const isDropAllowed =
Expand Down
Expand Up @@ -33,10 +33,8 @@ function selectBackgroundColor(isActive: boolean, canDrop: boolean) {
}

const Dustbin: React.FC<DustbinProps> = ({ allowedDropEffect }) => {
const ref = React.useRef(null)
const { canDrop, isOver } = useDrop({
ref,
type: ItemTypes.BOX,
const [{ canDrop, isOver }, ref] = useDrop({
accept: ItemTypes.BOX,
drop: () => ({
name: `${allowedDropEffect} Dustbin`,
allowedDropEffect,
Expand Down
10 changes: 10 additions & 0 deletions packages/examples-hooks/src/01 Dustbin/Copy or Move/interfaces.ts
@@ -0,0 +1,10 @@
export interface DragItem {
type: string
name: string
}

export interface DropResult {
name: string
dropEffect: string
allowedDropEffect: string
}
Expand Up @@ -22,11 +22,9 @@ export interface BoxProps {
}

const Box: React.FC<BoxProps> = ({ name, type, isDropped }) => {
const ref = React.useRef(null)
const { opacity } = useDrag({
ref,
type,
begin: () => ({ name }),
const item = { name, type }
const [{ opacity }, ref] = useDrag({
item,
collect: monitor => ({
opacity: monitor.isDragging() ? 0.4 : 1,
}),
Expand Down

0 comments on commit 46b7893

Please sign in to comment.