Skip to content

Commit

Permalink
refactor: use spec functions in useDrag/useDrop (#3056)
Browse files Browse the repository at this point in the history
  • Loading branch information
darthtrevino committed Feb 22, 2021
1 parent 339dd7a commit 28ef6b2
Show file tree
Hide file tree
Showing 45 changed files with 465 additions and 391 deletions.
5 changes: 4 additions & 1 deletion .yarn/versions/9c51b640.yml
@@ -1,8 +1,11 @@
releases:
react-dnd: patch
dnd-core: patch
react-dnd: major
react-dnd-html5-backend: minor

declined:
- react-dnd-test-backend
- react-dnd-touch-backend
- react-dnd-documentation
- react-dnd-examples-decorators
- react-dnd-examples-hooks
Expand Down
6 changes: 3 additions & 3 deletions packages/dnd-core/src/classes/DragDropMonitorImpl.ts
Expand Up @@ -79,7 +79,7 @@ export class DragDropMonitorImpl implements DragDropMonitor {
return false
}
const source = this.registry.getSource(sourceId)
invariant(source, 'Expected to find a valid source.')
invariant(source, `Expected to find a valid source. sourceId=${sourceId}`)

if (this.isDragging()) {
return false
Expand All @@ -94,7 +94,7 @@ export class DragDropMonitorImpl implements DragDropMonitor {
return false
}
const target = this.registry.getTarget(targetId)
invariant(target, 'Expected to find a valid target.')
invariant(target, `Expected to find a valid target. targetId=${targetId}`)

if (!this.isDragging() || this.didDrop()) {
return false
Expand All @@ -117,7 +117,7 @@ export class DragDropMonitorImpl implements DragDropMonitor {
return false
}
const source = this.registry.getSource(sourceId, true)
invariant(source, 'Expected to find a valid source.')
invariant(source, `Expected to find a valid source. sourceId=${sourceId}`)

if (!this.isDragging() || !this.isSourcePublic()) {
return false
Expand Down
88 changes: 50 additions & 38 deletions packages/docsite/markdown/docs/00 Quick Start/Tutorial.md
Expand Up @@ -454,15 +454,15 @@ The preparation work is done now. Let's make the `Knight` draggable!

## Make the Knight Draggable

The [`useDrag`](/docs/api/use-drag) hook accepts a specification object. In this object, `item.type` is set to the constant we just defined, so now we need to write a collecting function.
The [`useDrag`](/docs/api/use-drag) hook accepts a memoization function that returns a specification object. In this object, `item.type` is set to the constant we just defined, so now we need to write a collecting function.

```jsx
const [{ isDragging }, drag] = useDrag({
const [{ isDragging }, drag] = useDrag(() => ({
item: { type: ItemTypes.KNIGHT },
collect: (monitor) => ({
isDragging: !!monitor.isDragging()
})
})
}))
```

Let's break this down:
Expand All @@ -481,12 +481,12 @@ import { ItemTypes } from './Constants'
import { useDrag } from 'react-dnd'

function Knight() {
const [{isDragging}, drag] = useDrag({
const [{isDragging}, drag] = useDrag(() => ({
item: { type: ItemTypes.KNIGHT },
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
})
}), [])

return (
<div
Expand Down Expand Up @@ -556,24 +556,30 @@ function renderPiece(x, y, [knightX, knightY]) {
Let's now wrap the `BoardSquare` with a [`useDrop`](/docs/api/use-drop) hook. I'm going to write a drop target specification that only handles the `drop` event:

```jsx
const [, drop] = useDrop({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y)
})
const [, drop] = useDrop(
() => ({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y)
}),
[x, y]
)
```

See? The `drop` method has the `props` of the `BoardSquare` in scope, so it knows _where_ to move the knight when it drops. In a real app, I might also use `monitor.getItem()` to retrieve _the dragged item_ that the drag source returned from `beginDrag`, but since we only have a single draggable thing in the whole application, I don't need it.

In my collecting function I'm going to ask the monitor whether the pointer is currently over the `BoardSquare` so I can highlight it:

```jsx
const [{ isOver }, drop] = useDrop({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver()
})
})
const [{ isOver }, drop] = useDrop(
() => ({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver()
})
}),
[x, y]
)
```

After changing the `render` function to connect the drop target and show the highlight overlay, here is what `BoardSquare` came to be:
Expand All @@ -587,13 +593,13 @@ import { useDrop } from 'react-dnd'

function BoardSquare({ x, y, children }) {
const black = (x + y) % 2 === 1
const [{ isOver }, drop] = useDrop({
const [{ isOver }, drop] = useDrop(() => ({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y),
collect: monitor => ({
isOver: !!monitor.isOver(),
}),
})
}), [x, y])

return (
<div
Expand Down Expand Up @@ -633,15 +639,18 @@ This is starting to look good! There is just one change left to complete this tu
Thankfully, it is really easy to do with React DnD. I just need to define a `canDrop` method in my drop target specification:

```jsx
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.KNIGHT,
canDrop: () => canMoveKnight(x, y),
drop: () => moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
})
const [{ isOver, canDrop }, drop] = useDrop(
() => ({
accept: ItemTypes.KNIGHT,
canDrop: () => canMoveKnight(x, y),
drop: () => moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
}),
[x, y]
)
```

I'm also adding `monitor.canDrop()` to my collecting function, as well as some overlay rendering code to the component:
Expand All @@ -655,15 +664,18 @@ import { useDrop } from 'react-dnd'

function BoardSquare({ x, y, children }) {
const black = (x + y) % 2 === 1
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y),
canDrop: () => canMoveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
})
const [{ isOver, canDrop }, drop] = useDrop(
() => ({
accept: ItemTypes.KNIGHT,
drop: () => moveKnight(x, y),
canDrop: () => canMoveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
}),
[x, y]
)

return (
<div
Expand Down Expand Up @@ -694,12 +706,12 @@ The last thing I want to demonstrate is drag preview customization. Sure, the br
We are lucky again, because it is easy to do with React DnD. We just need to use the preview ref provided by the `useDrag` hook.

```jsx
const [{ isDragging }, drag, preview] = useDrag({
const [{ isDragging }, drag, preview] = useDrag(() => ({
item: { type: ItemTypes.KNIGHT },
collect: (monitor) => ({
isDragging: !!monitor.isDragging()
})
})
}))
```

This lets us connect up a `dragPreview` in `render` method, just like we used for drag items. `react-dnd` also provides a utility component, `DragPreviewImage`, which presents an image as a drag preview using this ref.
Expand Down
22 changes: 11 additions & 11 deletions packages/docsite/markdown/docs/03 Using Hooks/HooksOverview.md
Expand Up @@ -26,39 +26,39 @@ To start using hooks, let's make a box draggable.
import { useDrag } from 'react-dnd'

function Box() {
const [{ isDragging }, drag, dragPreview] = useDrag({
const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
// "type" is required. It is used by the "accept" specification of drop targets.
item: { type: 'BOX' },
// The collect function utilizes a "monitor" instance (see the Overview for what this is)
// to pull important pieces of state from the DnD system.
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
}))

return (
{/* This is optional. The dragPreview will be attached to the dragSource by default */}
<div ref={dragPreview} style={{ opacity: isDragging ? 0.5 : 1}}>
{/* The drag ref marks this node as being the "pick-up" node */}
<div role="Handle" ref={drag}>
</div>
)
})
return (
{/* This is optional. The dragPreview will be attached to the dragSource by default */}
<div ref={dragPreview} style={{ opacity: isDragging ? 0.5 : 1}}>
{/* The drag ref marks this node as being the "pick-up" node */}
<div role="Handle" ref={drag}>
</div>
)
}
```

Now, let's make something for this to drag into.

```jsx
function Bucket() {
const [{ canDrop, isOver }, drop] = useDrop({
const [{ canDrop, isOver }, drop] = useDrop(() => ({
// The type (or types) to accept - strings or symbols
accept: 'BOX',
// Props to collect
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
})
}))

return (
<div
Expand Down
7 changes: 4 additions & 3 deletions packages/docsite/markdown/docs/03 Using Hooks/useDrag.md
Expand Up @@ -15,9 +15,9 @@ The `useDrag`hook provides a way to wire your component into the DnD system as a
import { useDrag } from 'react-dnd'

function DraggableComponent(props) {
const [collected, drag, dragPreview] = useDrag({
const [collected, drag, dragPreview] = useDrag(() => ({
item: { id, type }
})
}))
return collected.isDragging ?
<div ref={dragPreview> : (
<div ref={drag} {...collected}>
Expand All @@ -29,7 +29,8 @@ function DraggableComponent(props) {
#### Parameters
- **`spec`** A specification object, see below for details on how to construct this
- **`spec`** A function that creates a specification object (recommended), or a specification object. See below for details on how to construct this
- **`deps`** A dependency array used for memoization. This behaves like the built-in `useMemo` React hook. The default value is an empty array for function spec, and an array containing the spec for an object spec.
#### Return Value Array
Expand Down
7 changes: 4 additions & 3 deletions packages/docsite/markdown/docs/03 Using Hooks/useDrop.md
Expand Up @@ -15,17 +15,18 @@ The `useDrop` hook provides a way for you to wire in your component into the DnD
import { useDrop } from 'react-dnd'

function myDropTarget(props) {
const [collectedProps, drop] = useDrop({
const [collectedProps, drop] = useDrop(() => ({
accept
})
}))

return <div ref={drop}>Drop Target</div>
}
```

#### Parameters

- **`spec`** A specification object, see below for details on how to construct this
- **`spec`** A function that creates a specification object (recommended), or a specification object. See below for details on how to construct this
- **`deps`** A dependency array used for memoization. This behaves like the built-in `useMemo` React hook. The default value is an empty array for function spec, and an array containing the spec for an object spec.

#### Return Value Array

Expand Down
15 changes: 9 additions & 6 deletions packages/docsite/markdown/docs/docsRoot.md
Expand Up @@ -26,12 +26,15 @@ import { ItemTypes } from './Constants'
* Your Component
*/
export default function Card({ isDragging, text }) {
const [{ opacity }, dragRef] = useDrag({
item: { type: ItemTypes.CARD, text },
collect: (monitor) => ({
opacity: monitor.isDragging() ? 0.5 : 1
})
})
const [{ opacity }, dragRef] = useDrag(
() => ({
item: { type: ItemTypes.CARD, text },
collect: (monitor) => ({
opacity: monitor.isDragging() ? 0.5 : 1
})
}),
[]
)
return (
<div ref={dragRef} style={{ opacity }}>
{text}
Expand Down
19 changes: 11 additions & 8 deletions packages/examples-hooks/src/00-chessboard/BoardSquare.tsx
Expand Up @@ -17,15 +17,18 @@ export const BoardSquare: FC<BoardSquareProps> = ({
children,
game,
}: BoardSquareProps) => {
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.KNIGHT,
canDrop: () => game.canMoveKnight(x, y),
drop: () => game.moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
const [{ isOver, canDrop }, drop] = useDrop(
() => ({
accept: ItemTypes.KNIGHT,
canDrop: () => game.canMoveKnight(x, y),
drop: () => game.moveKnight(x, y),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
})
[game],
)
const black = (x + y) % 2 === 1

return (
Expand Down
13 changes: 8 additions & 5 deletions packages/examples-hooks/src/00-chessboard/Knight.tsx
Expand Up @@ -10,12 +10,15 @@ const knightStyle: CSSProperties = {
}

export const Knight: FC = () => {
const [{ isDragging }, drag, preview] = useDrag({
item: { type: ItemTypes.KNIGHT },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
const [{ isDragging }, drag, preview] = useDrag(
() => ({
item: { type: ItemTypes.KNIGHT },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}),
})
[],
)

return (
<>
Expand Down

0 comments on commit 28ef6b2

Please sign in to comment.