Skip to content

Commit

Permalink
New Feature Proposal: Connect with Ref Objects (#1267)
Browse files Browse the repository at this point in the history
* feat: add the ability to connect with a React.RefObject instead of a function

This will be a more straightforward way of assigning roles to react nodes. It also provides some similarity to the hooks-api.

`return connectDragSource(<div>...)`

becomes

`return <div ref={dragSource} />`

* feat: allow composite usage by allowing client ref injection

* fix: allow options to be passed into connectors when using ref parameters

* fix: prevent re-subscription to the HTML5Backend if nothing changes between connect() invocations
  • Loading branch information
darthtrevino committed Mar 18, 2019
1 parent 0d717e9 commit 0958d67
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 129 deletions.
3 changes: 1 addition & 2 deletions packages/dnd-core/package.json
Expand Up @@ -10,8 +10,7 @@
"build:cjs": "tsc -b tsconfig.cjs.json",
"build": "run-p build:*",
"clean": "rimraf lib",
"watch": "tsc -w --preserveWatchOutput",
"start": "npm run watch",
"start": "tsc -b tsconfig.cjs.json -w --preserveWatchOutput",
"test": "run-s clean build"
},
"repository": {
Expand Down
3 changes: 1 addition & 2 deletions packages/documentation/package.json
Expand Up @@ -49,8 +49,7 @@
},
"scripts": {
"clean": "rimraf .cache public apidocs static/examples_js",
"develop": "gatsby develop",
"start": "npm run develop",
"start": "gatsby develop",
"copy_example_source": "cp -r ../examples/lib/docs static/examples_js",
"format_example_source": "prettier 'static/examples_js/**/*.js*' --use-tabs=false --write",
"build_static_site": "gatsby build --prefix-paths",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples-hooks/package.json
Expand Up @@ -17,7 +17,7 @@
"build:docs": "tsc -b tsconfig.docs.json",
"build": "run-p build:*",
"test": "run-s clean build",
"start": "tsc -w --preserveWatchOutput"
"start": "tsc -b tsconfig.cjs.json -w --preserveWatchOutput"
},
"dependencies": {
"@types/faker": "^4.1.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/package.json
Expand Up @@ -17,7 +17,7 @@
"build:docs": "tsc -b tsconfig.docs.json",
"build": "run-p build:*",
"test": "run-s clean build",
"start": "tsc -w --preserveWatchOutput"
"start": "tsc -b tsconfig.cjs.json -w --preserveWatchOutput"
},
"dependencies": {
"@types/faker": "^4.1.5",
Expand Down
19 changes: 9 additions & 10 deletions packages/examples/src/01 Dustbin/Single Target/Box.tsx
@@ -1,10 +1,5 @@
import React from 'react'
import {
ConnectDragSource,
DragSource,
DragSourceConnector,
DragSourceMonitor,
} from 'react-dnd'
import { DragSource, DragSourceConnector, DragSourceMonitor } from 'react-dnd'
import ItemTypes from './ItemTypes'

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

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

const boxSource = {
Expand All @@ -45,19 +40,23 @@ const boxSource = {

class Box extends React.Component<BoxProps & BoxCollectedProps> {
public render() {
const { isDragging, connectDragSource } = this.props
const { isDragging, dragSource } = this.props
const { name } = this.props
const opacity = isDragging ? 0.4 : 1

return connectDragSource(<div style={{ ...style, opacity }}>{name}</div>)
return (
<div ref={dragSource} style={{ ...style, opacity }}>
{name}
</div>
)
}
}

export default DragSource(
ItemTypes.BOX,
boxSource,
(connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
connectDragSource: connect.dragSource(),
dragSource: connect.dragSourceRef,
isDragging: monitor.isDragging(),
}),
)(Box)
23 changes: 8 additions & 15 deletions packages/examples/src/01 Dustbin/Single Target/Dustbin.tsx
@@ -1,10 +1,5 @@
import React from 'react'
import {
DropTarget,
DropTargetConnector,
DropTargetMonitor,
ConnectDropTarget,
} from 'react-dnd'
import { DropTarget, DropTargetConnector, DropTargetMonitor } from 'react-dnd'
import ItemTypes from './ItemTypes'

const style: React.CSSProperties = {
Expand All @@ -21,20 +16,18 @@ const style: React.CSSProperties = {
}

const boxTarget = {
drop() {
return { name: 'Dustbin' }
},
drop: () => ({ name: 'Dustbin' }),
}

export interface DustbinProps {
canDrop: boolean
isOver: boolean
connectDropTarget: ConnectDropTarget
dropTarget: React.RefObject<any>
}

class Dustbin extends React.Component<DustbinProps> {
public render() {
const { canDrop, isOver, connectDropTarget } = this.props
const { canDrop, isOver, dropTarget } = this.props
const isActive = canDrop && isOver

let backgroundColor = '#222'
Expand All @@ -44,10 +37,10 @@ class Dustbin extends React.Component<DustbinProps> {
backgroundColor = 'darkkhaki'
}

return connectDropTarget(
<div style={{ ...style, backgroundColor }}>
return (
<div ref={dropTarget} style={{ ...style, backgroundColor }}>
{isActive ? 'Release to drop' : 'Drag a box here'}
</div>,
</div>
)
}
}
Expand All @@ -56,7 +49,7 @@ export default DropTarget(
ItemTypes.BOX,
boxTarget,
(connect: DropTargetConnector, monitor: DropTargetMonitor) => ({
connectDropTarget: connect.dropTarget(),
dropTarget: connect.dropTargetRef,
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
Expand Down
3 changes: 1 addition & 2 deletions packages/react-dnd-html5-backend/package.json
Expand Up @@ -18,8 +18,7 @@
"bundle:min": "webpack --mode production --output-filename=ReactDnDHTML5Backend.min.js",
"build": "run-p bundle:* transpile",
"test": "run-s clean build",
"watch": "tsc -w --preserveWatchOutput",
"start": "npm run watch"
"start": "tsc -b tsconfig.esm.json -w --preserveWatchOutput"
},
"dependencies": {
"dnd-core": "^7.2.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/react-dnd/package.json
Expand Up @@ -18,8 +18,7 @@
"transpile": "run-p transpile:*",
"build": "run-p bundle:* transpile",
"test": "run-s clean build",
"watch": "tsc -w --preserveWatchOutput",
"start": "npm run watch"
"start": "tsc -b tsconfig.cjs.json -w --preserveWatchOutput"
},
"dependencies": {
"dnd-core": "^7.2.0",
Expand Down
148 changes: 89 additions & 59 deletions packages/react-dnd/src/createSourceConnector.ts
@@ -1,92 +1,122 @@
declare var require: any

import * as React from 'react'
import wrapConnectorHooks from './wrapConnectorHooks'
import { Backend, Unsubscribe, Identifier } from 'dnd-core'
import { isRef } from './hooks/util'
const shallowEqual = require('shallowequal')

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

let currentDragSourceNode: any
let currentDragSourceOptions: any
let disconnectCurrentDragSource: Unsubscribe | undefined

let currentDragPreviewNode: any
let currentDragPreviewOptions: any
let disconnectCurrentDragPreview: Unsubscribe | undefined
let handlerId: Identifier

// The drop target may either be attached via ref or connect function
let dragSourceRef = React.createRef<any>()
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 dragPreviewNode: any
let dragPreviewOptions: any
let disconnectDragPreview: Unsubscribe | undefined

let lastConnectedHandlerId: Identifier | null = null
let lastConnectedDragSource: any = null
let lastConnectedDragSourceOptions: any = null
let lastConnectedDragPreview: any = null
let lastConnectedDragPreviewOptions: any = null

function reconnectDragSource() {
if (disconnectCurrentDragSource) {
disconnectCurrentDragSource()
disconnectCurrentDragSource = undefined
const dragSource = dragSourceNode || dragSourceRef.current
if (!handlerId || !dragSource) {
return
}

if (currentHandlerId && currentDragSourceNode) {
disconnectCurrentDragSource = backend.connectDragSource(
currentHandlerId,
currentDragSourceNode,
currentDragSourceOptions,
// if nothing has changed then don't resubscribe
if (
lastConnectedHandlerId !== handlerId ||
lastConnectedDragSource !== dragSource ||
!shallowEqual(lastConnectedDragSourceOptions, dragSourceOptions)
) {
if (disconnectDragSource) {
disconnectDragSource()
disconnectDragSource = undefined
}

lastConnectedHandlerId = handlerId
lastConnectedDragSource = dragSource
lastConnectedDragSourceOptions = dragSourceOptions
disconnectDragSource = backend.connectDragSource(
handlerId,
dragSource,
dragSourceOptions,
)
}
}

function reconnectDragPreview() {
if (disconnectCurrentDragPreview) {
disconnectCurrentDragPreview()
disconnectCurrentDragPreview = undefined
const dragPreview = dragPreviewNode || dragPreviewRef.current
if (!handlerId || !dragPreview) {
return
}

if (currentHandlerId && currentDragPreviewNode) {
disconnectCurrentDragPreview = backend.connectDragPreview(
currentHandlerId,
currentDragPreviewNode,
currentDragPreviewOptions,
// if nothing has changed then don't resubscribe
if (
lastConnectedHandlerId !== handlerId ||
lastConnectedDragPreview !== dragPreview ||
!shallowEqual(lastConnectedDragPreviewOptions, dragPreviewOptions)
) {
if (disconnectDragPreview) {
disconnectDragPreview()
disconnectDragPreview = undefined
}
lastConnectedHandlerId = handlerId
lastConnectedDragPreview = dragPreview
lastConnectedDragPreviewOptions = dragPreviewOptions
disconnectDragPreview = backend.connectDragPreview(
handlerId,
dragPreview,
dragPreviewOptions,
)
}
}

function receiveHandlerId(handlerId: Identifier) {
if (handlerId === currentHandlerId) {
function receiveHandlerId(newHandlerId: Identifier) {
if (handlerId === newHandlerId) {
return
}

currentHandlerId = handlerId
handlerId = newHandlerId
reconnectDragSource()
reconnectDragPreview()
}

const hooks = wrapConnectorHooks({
dragSource: function connectDragSource(node: any, options: any) {
if (
node === currentDragSourceNode &&
shallowEqual(options, currentDragSourceOptions)
) {
return
}

currentDragSourceNode = node
currentDragSourceOptions = options

return {
receiveHandlerId,
hooks: wrapConnectorHooks({
dragSourceRef,
dragPreviewRef,
dragSource: function connectDragSource(node: any, options?: any) {
dragSourceOptions = options
if (isRef(node)) {
dragSourceRef = node
} else {
dragSourceNode = node
}
},

dragPreview: function connectDragPreview(node: any, options?: any) {
dragPreviewOptions = options
if (isRef(node)) {
dragPreviewRef = node
} else {
dragPreviewNode = node
}
},
}),
reconnect: () => {
reconnectDragSource()
},

dragPreview: function connectDragPreview(node: any, options: any) {
if (
node === currentDragPreviewNode &&
shallowEqual(options, currentDragPreviewOptions)
) {
return
}

currentDragPreviewNode = node
currentDragPreviewOptions = options

reconnectDragPreview()
},
})

return {
receiveHandlerId,
hooks,
}
}

0 comments on commit 0958d67

Please sign in to comment.