Skip to content

Commit

Permalink
Connector Ref Updates (#1278)
Browse files Browse the repository at this point in the history
* docs: add documentation about connector refs

* refactor: update the connector ref mechanism to use a function

* feat: allow options to be passed into connector-ref functions

* refactor: clean up createSourceConnector

* feat: udpate the connector api

* revert: pare back the connector APIs a bit, we'll start with just allowing ref arguments

* fix: unit tests
  • Loading branch information
darthtrevino committed Mar 19, 2019
1 parent 0958d67 commit 786ef85
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 73 deletions.
Expand Up @@ -7,15 +7,13 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# DragSourceConnector

`DragSourceConnector` is an object passed to a collecting function of the [`DragSource`](/docs/api/drag-source). Its methods return functions that let you assign the roles to your component's DOM nodes.
`DragSourceConnector` is an object passed to the _collecting function_ of the [`DragSource`](/docs/api/drag-source). It provides the ability to bind your React component to the Drag Source role.

### Methods
## Properties

Call the `DragSourceConnector` methods inside your _collecting function_. This will pass the returned functions to your component as props. You can then use them inside `render` or `componentDidMount` to indicate the DOM node roles. Internally they work by attaching a [callback ref](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) to the React elements you give them. These callbacks connect the DOM nodes of your component to the chosen DnD backend.
- **`dragSource() => (Element | Node | Ref, options?)`**: Returns a function that must be prop-injected into your component and used in that component's `render()` method. You may pass this function a react component, an DOM element, or a ref object to this method.

- **`dragSource() => (elementOrNode, options?)`**: Returns a function that must be used inside the component to assign the drag source role to a node. By returning `{ connectDragSource: connect.dragSource() }` from your collecting function, you can mark any React element as the draggable node. To do that, replace any `element` with `this.props.connectDragSource(element)` inside the `render` function.

- **`dragPreview() => (elementOrNode, options?)`**: Optional. Returns a function that may be used inside the component to assign the drag preview role to a node. By returning `{ connectDragPreview: connect.dragPreview() }` from your collecting function, you can mark any React element as the drag preview node. To do that, replace any `element` with `this.props.connectDragPreview(element)` inside the `render` function. The drag preview is the node that will be screenshotted by the [HTML5 backend](/docs/backends/html5) when the drag begins. For example, if you want to make something draggable by a small custom handle, you can mark this handle as the `dragSource()`, but also mark an outer, larger component node as the `dragPreview()`. Thus the larger drag preview appears on the screenshot, but only the smaller drag source is actually draggable. Another possible customization is passing an [`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image) instance to `dragPreview` from a lifecycle method like `componentDidMount`. This lets you use the actual images for drag previews. (Note that IE does not support this customization). See the example code below for the different usage examples.
- **`dragPreview() => (Element | Node | Ref, options?)`**_(optional)_: Returns a function that may be used inside the component to assign the drag preview role to a node. By returning `{ connectDragPreview: connect.dragPreview() }` from your collecting function, you can mark any React element as the drag preview node. To do that, replace any `element` with `this.props.connectDragPreview(element)` inside the `render` function. The drag preview is the node that will be screenshotted by the [HTML5 backend](/docs/backends/html5) when the drag begins. For example, if you want to make something draggable by a small custom handle, you can mark this handle as the `dragSource()`, but also mark an outer, larger component node as the `dragPreview()`. Thus the larger drag preview appears on the screenshot, but only the smaller drag source is actually draggable. Another possible customization is passing an [`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image) instance to `dragPreview` from a lifecycle method like `componentDidMount`. This lets you use the actual images for drag previews. (Note that IE does not support this customization). See the example code below for the different usage examples.

### Method Options

Expand All @@ -37,9 +35,7 @@ The functions returned by the connector methods also accept options. They need t

- `offsetY`: Optional. A number or null if not needed. By default, null. Specifies the vertical offset between the cursor and the drag preview element. If offsetY has a value, anchorY won't be used.

### Example

Check out [the tutorial](/docs/tutorial) for more real examples!
## Usage

```js
import React from 'react';
Expand All @@ -66,7 +62,7 @@ class ComponentWithCopyEffect {
);
}
});
ComponentWithCopyEffect = DragSource(/* ... */)(ComponentWithCopyEffect);
ComponentWithCopyEffect = DragSource(type, {/* ... */}, collect)(ComponentWithCopyEffect);

class ComponentWithHandle {
render() {
Expand All @@ -82,7 +78,7 @@ class ComponentWithHandle {
);
}
}
ComponentWithHandle = DragSource(/* ... */)(ComponentWithHandle);
ComponentWithHandle = DragSource(type, {/* ... */}, collect)(ComponentWithHandle);

class ComponentWithImagePreview {
componentDidMount() {
Expand All @@ -103,5 +99,9 @@ class ComponentWithImagePreview {
);
}
}
ComponentWithImagePreview = DragSource(/* ... */)(ComponentWithImagePreview);
ComponentWithImagePreview = DragSource(type, {/* ... */}, collect)(ComponentWithImagePreview);
```

### Example

Check out [the tutorial](/docs/tutorial) for more real examples!
Expand Up @@ -7,37 +7,36 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# DropTargetConnector

`DropTargetConnector` is an object passed to a collecting function of the [`DropTarget`](/docs/api/drop-target). Its only method `dropTarget()` returns a function that lets you assign the drop target role to one of your component's DOM nodes.
`DropTargetConnector` is an object passed to the _collecting function_ of the [`DropTarget`](/docs/api/drop-target). It provides the ability to bind your React component to the Drop Target role.

### Methods
## Properties

Call the `DropTargetConnector`'s `dropTarget()` method inside your _collecting function_. This will pass the returned function to your component as a prop. You can then use it inside `render` or `componentDidMount` to indicate a DOM node should react to drop target events. Internally it works by attaching a [callback ref](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) to the React element you give it. This callback connects the DOM node of your component to the chosen DnD backend.
- **`dropTarget() => (Element | Node | Ref)`**: Returns a function that must be prop-injected into your component and used in that component's `render()` method. You may pass this function a react component, an DOM element, or a ref object to this method.

- **`dropTarget() => (elementOrNode)`**: Returns a function that must be used inside the component to assign the drop target role to a node. By returning `{ connectDropTarget: connect.dropTarget() }` from your collecting function, you can mark any React element as the droppable node. To do that, replace any `element` with `this.props.connectDropTarget(element)` inside the `render` function.

### Example

Check out [the tutorial](/docs/tutorial) for more real examples!
## Usage

```js
import React from 'react'
import { DropTarget } from 'react-dnd'

/* ... */

function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
}
}

class DropZone {
render() {
const { connectDropTarget } = this.props

return connectDropTarget(<div>You can drop here!</div>)
}
}

export default DropTarget(/* ... */)(DropZone)
export default DropTarget(
ItemTypes.Item,
{
/*...*/
},
(connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
}),
)(DropZone)
```

### Examples

Check out [the tutorial](/docs/tutorial) for more real examples!
19 changes: 13 additions & 6 deletions packages/examples/src/01 Dustbin/Single Target/Box.tsx
@@ -1,5 +1,10 @@
import React from 'react'
import { DragSource, DragSourceConnector, DragSourceMonitor } from 'react-dnd'
import {
DragSource,
DragSourceMonitor,
ConnectDragSource,
DragSourceConnector,
} from 'react-dnd'
import ItemTypes from './ItemTypes'

const style: React.CSSProperties = {
Expand All @@ -18,7 +23,7 @@ interface BoxProps {

interface BoxCollectedProps {
isDragging: boolean
dragSource: React.RefObject<any>
connectDragSource: ConnectDragSource
}

const boxSource = {
Expand All @@ -39,13 +44,15 @@ const boxSource = {
}

class Box extends React.Component<BoxProps & BoxCollectedProps> {
private dragSource: React.RefObject<HTMLDivElement> = React.createRef()

public render() {
const { isDragging, dragSource } = this.props
const { name } = this.props
const { name, isDragging, connectDragSource } = this.props
const opacity = isDragging ? 0.4 : 1
connectDragSource(this.dragSource)

return (
<div ref={dragSource} style={{ ...style, opacity }}>
<div ref={this.dragSource} style={{ ...style, opacity }}>
{name}
</div>
)
Expand All @@ -56,7 +63,7 @@ export default DragSource(
ItemTypes.BOX,
boxSource,
(connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
dragSource: connect.dragSourceRef,
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}),
)(Box)
18 changes: 13 additions & 5 deletions packages/examples/src/01 Dustbin/Single Target/Dustbin.tsx
@@ -1,5 +1,10 @@
import React from 'react'
import { DropTarget, DropTargetConnector, DropTargetMonitor } from 'react-dnd'
import {
DropTarget,
ConnectDropTarget,
DropTargetMonitor,
DropTargetConnector,
} from 'react-dnd'
import ItemTypes from './ItemTypes'

const style: React.CSSProperties = {
Expand All @@ -22,13 +27,16 @@ const boxTarget = {
export interface DustbinProps {
canDrop: boolean
isOver: boolean
dropTarget: React.RefObject<any>
connectDropTarget: ConnectDropTarget
}

class Dustbin extends React.Component<DustbinProps> {
private dropTarget: React.RefObject<HTMLDivElement> = React.createRef()

public render() {
const { canDrop, isOver, dropTarget } = this.props
const { canDrop, isOver, connectDropTarget } = this.props
const isActive = canDrop && isOver
connectDropTarget(this.dropTarget)

let backgroundColor = '#222'
if (isActive) {
Expand All @@ -38,7 +46,7 @@ class Dustbin extends React.Component<DustbinProps> {
}

return (
<div ref={dropTarget} style={{ ...style, backgroundColor }}>
<div ref={this.dropTarget} style={{ ...style, backgroundColor }}>
{isActive ? 'Release to drop' : 'Drag a box here'}
</div>
)
Expand All @@ -49,7 +57,7 @@ export default DropTarget(
ItemTypes.BOX,
boxTarget,
(connect: DropTargetConnector, monitor: DropTargetMonitor) => ({
dropTarget: connect.dropTargetRef,
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
Expand Down
23 changes: 13 additions & 10 deletions packages/react-dnd/src/createSourceConnector.ts
Expand Up @@ -3,19 +3,20 @@ import * as React from 'react'
import wrapConnectorHooks from './wrapConnectorHooks'
import { Backend, Unsubscribe, Identifier } from 'dnd-core'
import { isRef } from './hooks/util'
import { DragSourceOptions, DragPreviewOptions } from './interfaces'
const shallowEqual = require('shallowequal')

export default function createSourceConnector(backend: Backend) {
let handlerId: Identifier

// The drop target may either be attached via ref or connect function
let dragSourceRef = React.createRef<any>()
let dragSourceRef: React.RefObject<any> | null = null
let dragSourceNode: any
let dragSourceOptions: any
let disconnectDragSource: Unsubscribe | undefined

// The drag preview may either be attached via ref or connect function
let dragPreviewRef = React.createRef<any>()
let dragPreviewRef: React.RefObject<any> | null = null
let dragPreviewNode: any
let dragPreviewOptions: any
let disconnectDragPreview: Unsubscribe | undefined
Expand All @@ -27,7 +28,8 @@ export default function createSourceConnector(backend: Backend) {
let lastConnectedDragPreviewOptions: any = null

function reconnectDragSource() {
const dragSource = dragSourceNode || dragSourceRef.current
const dragSource =
dragSourceNode || (dragSourceRef && dragSourceRef.current)
if (!handlerId || !dragSource) {
return
}
Expand Down Expand Up @@ -55,7 +57,8 @@ export default function createSourceConnector(backend: Backend) {
}

function reconnectDragPreview() {
const dragPreview = dragPreviewNode || dragPreviewRef.current
const dragPreview =
dragPreviewNode || (dragPreviewRef && dragPreviewRef.current)
if (!handlerId || !dragPreview) {
return
}
Expand Down Expand Up @@ -94,18 +97,18 @@ export default function createSourceConnector(backend: Backend) {
return {
receiveHandlerId,
hooks: wrapConnectorHooks({
dragSourceRef,
dragPreviewRef,
dragSource: function connectDragSource(node: any, options?: any) {
dragSource: (
node: Element | React.ReactElement | React.Ref<any>,
options?: DragSourceOptions,
) => {
dragSourceOptions = options
if (isRef(node)) {
dragSourceRef = node
dragSourceRef = node as React.RefObject<any>
} else {
dragSourceNode = node
}
},

dragPreview: function connectDragPreview(node: any, options?: any) {
dragPreview: (node: any, options?: DragPreviewOptions) => {
dragPreviewOptions = options
if (isRef(node)) {
dragPreviewRef = node
Expand Down
8 changes: 4 additions & 4 deletions packages/react-dnd/src/createTargetConnector.ts
Expand Up @@ -8,7 +8,7 @@ const shallowEqual = require('shallowequal')
export default function createTargetConnector(backend: Backend) {
let handlerId: Identifier
// The drop target may either be attached via ref or connect function
let dropTargetRef = React.createRef<any>()
let dropTargetRef: React.RefObject<any> | null = null
let dropTargetNode: any
let dropTargetOptions: any
let disconnectDropTarget: Unsubscribe | undefined
Expand All @@ -18,7 +18,8 @@ export default function createTargetConnector(backend: Backend) {
let lastConnectedDropTargetOptions: any = null

function reconnectDropTarget() {
const dropTarget = dropTargetNode || dropTargetRef.current
const dropTarget =
dropTargetNode || (dropTargetRef && dropTargetRef.current)
if (!handlerId || !dropTarget) {
return
}
Expand Down Expand Up @@ -56,8 +57,7 @@ export default function createTargetConnector(backend: Backend) {
return {
receiveHandlerId,
hooks: wrapConnectorHooks({
dropTargetRef,
dropTarget: function connectDropTarget(node: any, options: any) {
dropTarget: (node: any, options: any) => {
dropTargetOptions = options
if (isRef(node)) {
dropTargetRef = node
Expand Down
19 changes: 1 addition & 18 deletions packages/react-dnd/src/interfaces/classApi.ts
Expand Up @@ -121,6 +121,7 @@ export type ConnectedElement =
| React.ReactElement
| Element
| null

export type DragElementWrapper<Options> = <Props>(
elementOrNode: ConnectedElement,
options?: Options,
Expand All @@ -134,24 +135,11 @@ export type ConnectDragPreview = DragElementWrapper<DragPreviewOptions>
* Its methods return functions that let you assign the roles to your component's DOM nodes.
*/
export interface DragSourceConnector {
/**
* A React ref object to attach to the drag source. This replaces the dragSource() function described below.
*/
dragSourceRef: React.RefObject<any>

/**
* A React ref object to attach to the drag preview. This replaces the dragPreview() function described below.
*/
dragPreviewRef: React.RefObject<any>

/**
* Returns a function that must be used inside the component to assign the drag source role to a node. By
* returning { connectDragSource: connect.dragSource() } from your collecting function, you can mark any React
* element as the draggable node. To do that, replace any element with this.props.connectDragSource(element) inside
* the render function.
*
* @param elementOrNode
* @param options
*/
dragSource(): ConnectDragSource

Expand All @@ -174,11 +162,6 @@ export interface DragSourceConnector {
* that lets you assign the drop target role to one of your component's DOM nodes.
*/
export interface DropTargetConnector {
/**
* A React ref object to attach to the drop target. This replaces the dropTarget() function described below.
*/
dropTargetRef: React.RefObject<any>

/**
* Returns a function that must be used inside the component to assign the drop target role to a node.
* By returning { connectDropTarget: connect.dropTarget() } from your collecting function, you can mark any React element
Expand Down

0 comments on commit 786ef85

Please sign in to comment.