Skip to content

Commit

Permalink
Hooks Ref'ing Updates (#1280)
Browse files Browse the repository at this point in the history
* docs: clean up native file example

* test: add an example case to demonstrate flaw with current hook api

* fix: replace refs in useDrag,useDrop with connector functions

* feat: work on hooks, perf improvements

* refactor: clean up usedrag/usedrop hooks

* refactor: clean up the number of hook files

* feat: use callback refs in hooks api

* feat: memoize drag/drop specs

* fix: class api typings

* feat: remove the 'preview' option of the drag spec

* feat: update previews, documentation

* refactor: move isRef

* refactor: create SourceConnector, TargetConneector classes

* fix: hooks isuses

* refactor: make the hooks field constant in the connector classes

* fix: reconnect connectors on target change

* docs: clean up knight component

* fix: add typings to useCollector arguments

* fix: tighten typings on dragdrop monitors

* fix: some liveness issues, hooks are still wonky atm

* refactor: decouple useCollector/useMonitor output from connector types

* docs: update usedetachedcomponent docs

* fix: correct issues with detached previews, rename the hook to useDetachedPreview

* docs: remove previewproperty from dragspec in useDetachedPreview

* revert: eliminate implicit memoization. BYOM(emoization of specs)

* refactor: clarify the ref mechanism is decorateHandler. Remove recompose since the ref objects will be harmless on FCs

* fix: scope down the drag preview wrapper

Because of the limitations with drag previews, we can't use arbitrary components, only Image elements. Because of this, we've retooled the DisconnectedDragPreview HOC to be just a DragPreviewImage component which can render disconnected images.

* docs: add drag-preview-image docs to sidebar

* refactor: replace the portaling mechanism with a renderless component

This mechanism for showing drag previews is much, much simpler, but still allows clients to use image previews as components.

* docs: add some notes to the new examples
  • Loading branch information
darthtrevino committed Mar 21, 2019
1 parent aa584be commit 7779463
Show file tree
Hide file tree
Showing 80 changed files with 1,042 additions and 817 deletions.
6 changes: 5 additions & 1 deletion packages/dnd-core/src/utils/dirtiness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const intersection = require('lodash/intersection')

export const NONE: string[] = []
export const ALL: string[] = []
// Add these flags for debug
;(NONE as any).__IS_NONE__ = true
;(ALL as any).__IS_ALL__ = true

/**
* Determines if the given handler IDs are dirty or not.
Expand All @@ -23,5 +26,6 @@ export function areDirty(
return true
}

return intersection(handlerIds, dirtyIds).length > 0
const commonIds = intersection(handlerIds, dirtyIds)
return commonIds.length > 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
path: '/docs/api/drag-preview-image'
title: 'DragPreviewImage'
---

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

# DragPreviewImage

A Component to render an HTML Image element as a disconnected drag preview.

### Usage

```js
import HTML5Backend from 'react-dnd-html5-backend'
import { DragSource, DragPreviewImage } from 'react-dnd'

function DraggableHouse({ connectDragSource, connectDragPreview }) {
return (
<>
<DragPreviewImage src="house_dragged.png" connect={connectDragPreview} />
<div ref={connectDragSource}>🏠</div>
</>
)
}
export default DragSource(
/* ... */
(connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
}),
)
```

### Props

- **`connect`**: Required. The drag preview connector function
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,30 @@ import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ as dnd }
const { useDrag } = dnd

function DraggableComponent(props) {
const [collectedProps, ref] = useDrag({
const [collectedProps, drag] = useDrag({
item: { id, type },
})
return <div ref={ref}>...</div>
return <div ref={drag}>...</div>
}
```

#### Parameters

- **`spec`** Specification object, see below for details on how to construct this
- **`spec`** A specification object, see below for details on how to construct this
- **`memoization parameters`** - values to use when rebuilding the memoized specification

#### Return Value Array

- **`Index 0`**: An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
- **`Index 1`**: The React ref to use. This is automatically created if no `ref` field is defined on the specification object. The ref must be attached to the draggable portion of the DOM.
- **`Index 1`**: A connector function for the drag source. This must be attached to the draggable portion of the DOM.
- **`Index 2`**: A connector function for the drag preview. This may be attached to the preview portion of the DOM.

### Specification Object Members

- **`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` **must be set**, and it must be either a string, an ES6 symbol`. Only the [drop targets](/docs/api/drop-target) registered for the same type will react to this item. Read the [overview](/docs/overview) to learn more about the items and types.

- **`ref`**: Optional. A ref object to use to attach to the draggable element. If this is unset, one will be created ad returned.

- **`preview`**: Optional. An HTML Element or a ref object attached to the dragPreview element. Consider using the `useDragPreview` hook to create this for you.

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

* **`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.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,28 @@ import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ as dnd }
const { useDrop } = dnd

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

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

#### Parameters

- **`spec`** Specification object, see below for details on how to construct this
- **`spec`** A specification object, see below for details on how to construct this
- **`memoization parameters`** - values to use when rebuilding the memoized specification

#### Return Value Array

- **`Index 0`**: An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
- **`Index 1`**: The React ref to use. This is automatically created if no `ref` field is defined on the specification object. The ref must be attached to the droppable area of the DOM.
- **`Index 1`**: A connector function for the drop target. This must be attached to the drop-target portion of the DOM.

### Specification Object Members

- **`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.

* **`ref`**: Optional. A ref object to use to attach to the draggable element. If this is unset, one will be created ad returned.

* **`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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ title: 'Handles and Previews'

React DnD lets you choose the draggable node, as well as the drag
preview node in your component's `render` function.

You may also use an [Image](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image) instance that you created programmatically once it has loaded.

<customize-handles-and-previews></customize-handles-and-previews>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
path: '/examples/other/drag-source-rerender'
title: 'Drag Source Rerender'
---

[JavaScript](https://github.com/react-dnd/react-dnd/tree/gh-pages/examples_js/06%20Other/Drag%20Source%20Rerender)
[TypeScript](https://github.com/react-dnd/react-dnd/tree/master/packages/examples/src/06%20Other/Drag%20Source%20Rerender)

Regression example using a drop target that's connected as a child

<other-drag-source-rerender></other-drag-source-rerender>
8 changes: 8 additions & 0 deletions packages/documentation/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export const APIPages: PageGroup[] = [
location: '/docs/api/drag-layer',
title: 'DragLayer',
},
DRAG_PREVIEW_IMAGE: {
location: '/docs/api/drag-preview-image',
title: 'DragPreviewImage',
},
DRAG_DROP_CONTEXT: {
location: '/docs/api/drag-drop-context',
title: 'DragDropContext',
Expand Down Expand Up @@ -242,6 +246,10 @@ export const ExamplePages: PageGroup[] = [
{
title: 'Other Cases',
pages: {
OTHER_DRAG_SOURCE_RERENDER: {
location: '/examples/other/drag-source-rerender',
title: 'Drag Source Rerender',
},
OTHER_NATIVE_FILES: {
location: '/examples/other/native-files',
title: 'Native Files',
Expand Down
20 changes: 11 additions & 9 deletions packages/examples-hooks/src/00 Chessboard/BoardSquare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,32 @@ export interface BoardSquareProps {
children: any
}

export const BoardSquare: React.FC<BoardSquareProps> = (
props: BoardSquareProps,
) => {
const [{ isOver, canDrop }, ref] = useDrop({
export const BoardSquare: React.FC<BoardSquareProps> = ({
x,
y,
children,
}: BoardSquareProps) => {
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.KNIGHT,
canDrop: () => canMoveKnight(props.x, props.y),
drop: () => moveKnight(props.x, props.y),
canDrop: () => canMoveKnight(x, y),
drop: () => moveKnight(x, y),
collect: mon => ({
isOver: !!mon.isOver(),
canDrop: !!mon.canDrop(),
}),
})
const black = (props.x + props.y) % 2 === 1
const black = (x + y) % 2 === 1

return (
<div
ref={ref}
ref={drop}
style={{
position: 'relative',
width: '100%',
height: '100%',
}}
>
<Square black={black}>{props.children}</Square>
<Square black={black}>{children}</Square>
{isOver && !canDrop && <Overlay color="red" />}
{!isOver && canDrop && <Overlay color="yellow" />}
{isOver && canDrop && <Overlay color="green" />}
Expand Down
26 changes: 8 additions & 18 deletions packages/examples-hooks/src/00 Chessboard/Knight.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as React from 'react'
import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
import {
DragPreviewImage,
__EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__,
} from 'react-dnd'
import ItemTypes from './ItemTypes'
import knightImage from './knightImage'

const {
useDrag,
useDragPreview,
} = __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__

const knightStyle: React.CSSProperties = {
Expand All @@ -14,31 +16,19 @@ const knightStyle: React.CSSProperties = {
cursor: 'move',
}

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 item = { type: ItemTypes.KNIGHT }
const [DragPreview, preview] = useDragPreview(KnightDragPreview)
const [{ isDragging }, ref] = useDrag({
item,
preview,
const [{ isDragging }, drag, preview] = useDrag({
item: { type: ItemTypes.KNIGHT },
collect: mon => ({
isDragging: !!mon.isDragging(),
}),
})

return (
<>
<DragPreview />
<DragPreviewImage connect={preview} src={knightImage} />
<div
ref={ref}
ref={drag}
style={{
...knightStyle,
opacity: isDragging ? 0.5 : 1,
Expand Down
8 changes: 4 additions & 4 deletions packages/examples-hooks/src/01 Dustbin/Copy or Move/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from 'react'
import React from 'react'
import ItemTypes from '../Single Target/ItemTypes'

import { __EXPERIMENTAL_DND_HOOKS_THAT_MAY_CHANGE_AND_BREAK_MY_BUILD__ } from 'react-dnd'
Expand Down Expand Up @@ -27,7 +27,7 @@ interface DropResult {

const Box: React.FC<BoxProps> = ({ name }) => {
const item = { name, type: ItemTypes.BOX }
const [{ opacity }, ref] = useDrag({
const [{ opacity }, drag] = useDrag({
item,
end(dropResult?: DropResult) {
if (dropResult) {
Expand All @@ -50,13 +50,13 @@ const Box: React.FC<BoxProps> = ({ name }) => {
alert(alertMessage)
}
},
collect: monitor => ({
collect: (monitor: any) => ({
opacity: monitor.isDragging() ? 0.4 : 1,
}),
})

return (
<div ref={ref} style={{ ...style, opacity }}>
<div ref={drag} style={{ ...style, opacity }}>
{name}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,22 @@ function selectBackgroundColor(isActive: boolean, canDrop: boolean) {
}

const Dustbin: React.FC<DustbinProps> = ({ allowedDropEffect }) => {
const [{ canDrop, isOver }, ref] = useDrop({
const [{ canDrop, isOver }, drop] = useDrop({
accept: ItemTypes.BOX,
drop: () => ({
name: `${allowedDropEffect} Dustbin`,
allowedDropEffect,
}),
collect: monitor => ({
collect: (monitor: any) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
})

const isActive = canDrop && isOver
const backgroundColor = selectBackgroundColor(isActive, canDrop)
return (
<div ref={ref} style={{ ...style, backgroundColor }}>
<div ref={drop} style={{ ...style, backgroundColor }}>
{`Works with ${allowedDropEffect} drop effect`}
<br />
<br />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ export interface BoxProps {
}

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

return (
<div ref={ref} style={{ ...style, opacity }}>
<div ref={drag} style={{ ...style, opacity }}>
{isDropped ? <s>{name}</s> : name}
</div>
)
Expand Down

0 comments on commit 7779463

Please sign in to comment.