Skip to content

Commit

Permalink
Prepare Hooks API for Initial Release (#1251)
Browse files Browse the repository at this point in the history
* refactor: split up useMonitorSubscription into two hooks

* fix: resolve two-backend issue

When the DragDropProvider is removed from the DOM, it was not emitting a message to the DragDropManager so that it could handle refcounts properly and break-down the HTML5Backend.

This change adds a useEffect hook invocation to fire off an event that will cause the DnDManager to destroy the backend.

* refactor: update the react peer dependency of React-Dnd

* refactor: clean up monitor modules
  • Loading branch information
darthtrevino committed Mar 7, 2019
1 parent 5a4005b commit 7f78a8b
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 181 deletions.
51 changes: 25 additions & 26 deletions packages/documentation-examples/src/00 Chessboard/Board.tsx
@@ -1,47 +1,46 @@
import * as React from 'react'
import { BoardSquare } from './BoardSquare'
import { Knight } from './Knight'

export interface BoardProps {
knightPosition: [number, number]
}

export default class Board extends React.Component<BoardProps> {
public render() {
const squares = []
for (let i = 0; i < 64; i += 1) {
squares.push(this.renderSquare(i))
}

return (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexWrap: 'wrap',
}}
>
{squares}
</div>
)
}

private renderSquare(i: number) {
const Board: React.FC<BoardProps> = ({
knightPosition: [knightX, knightY],
}) => {
function renderSquare(i: number) {
const x = i % 8
const y = Math.floor(i / 8)

return (
<div key={i} style={{ width: '12.5%', height: '12.5%' }}>
<BoardSquare x={x} y={y}>
{this.renderPiece(x, y)}
{renderPiece(x, y)}
</BoardSquare>
</div>
)
}

private renderPiece(x: number, y: number) {
const [knightX, knightY] = this.props.knightPosition
function renderPiece(x: number, y: number) {
const isKnightHere = x === knightX && y === knightY
return isKnightHere ? <Knight /> : null
}

const squares = []
for (let i = 0; i < 64; i += 1) {
squares.push(renderSquare(i))
}
return (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexWrap: 'wrap',
}}
>
{squares}
</div>
)
}
export default Board
56 changes: 18 additions & 38 deletions packages/documentation-examples/src/00 Chessboard/index.tsx
Expand Up @@ -10,43 +10,23 @@ export interface ChessboardTutorialAppState {
/**
* Unlike the tutorial, export a component so it can be used on the website.
*/
export default class ChessboardTutorialApp extends React.Component<
{},
ChessboardTutorialAppState
> {
public state: ChessboardTutorialAppState = { knightPosition: [1, 7] }
private unobserve?: (() => void)
const ChessboardTutorialApp: React.FC = () => {
const [knightPos, setKnightPos] = React.useState<[number, number]>([1, 7])

public componentDidMount() {
this.unobserve = observe(this.handleChange)
}
public componentWillUnmount() {
if (this.unobserve) {
this.unobserve()
}
}

public render() {
const { knightPosition } = this.state
return (
<div
style={{
width: 500,
height: 500,
border: '1px solid gray',
}}
>
<Board knightPosition={knightPosition} />
</div>
)
}

private handleChange = (knightPosition: [number, number]) => {
const nextState = { knightPosition }
if (this.state) {
this.setState(nextState)
} else {
this.state = nextState
}
}
React.useEffect(() =>
observe((newPos: [number, number]) => setKnightPos(newPos)),
)
return (
<div
style={{
width: 500,
height: 500,
border: '1px solid gray',
}}
>
<Board knightPosition={knightPos} />
</div>
)
}

export default ChessboardTutorialApp
4 changes: 2 additions & 2 deletions packages/react-dnd/package.json
Expand Up @@ -45,7 +45,7 @@
"webpack-cli": "^3.2.3"
},
"peerDependencies": {
"react": ">= 16.3",
"react-dom": ">= 16.3"
"react": ">= 16.8",
"react-dom": ">= 16.8"
}
}
7 changes: 6 additions & 1 deletion packages/react-dnd/src/DragDropContext.tsx
Expand Up @@ -11,7 +11,6 @@ import { ContextComponent } from './interfaces'
const invariant = require('invariant')
const hoistStatics = require('hoist-non-react-statics')
const isClassComponent = require('recompose/isClassComponent').default

/**
* The React context type
*/
Expand Down Expand Up @@ -55,6 +54,12 @@ export const DragDropContextProvider: React.FC<
DragDropContextProviderProps<any>
> = ({ backend, context, debugMode, children }) => {
const contextValue = createChildContext(backend, context, debugMode)
React.useEffect(() => {
return () =>
contextValue.dragDropManager.dispatch({
type: 'DragDropContextProvider::Exiting',
})
})
return <Provider value={contextValue}>{children}</Provider>
}

Expand Down
17 changes: 17 additions & 0 deletions packages/react-dnd/src/DragDropContextType.ts
@@ -0,0 +1,17 @@
import * as React from 'react'
import { DragDropManager } from 'dnd-core'

/**
* The React context type
*/
export interface DragDropContext<BC> {
dragDropManager: DragDropManager<BC> | undefined
}

/**
* Create the React Context
*/
export const context = React.createContext<DragDropContext<any>>({
dragDropManager: undefined,
})
export const { Consumer, Provider } = context
7 changes: 4 additions & 3 deletions packages/react-dnd/src/DragSource.ts
@@ -1,6 +1,6 @@
declare var require: any
import * as React from 'react'
import { SourceType } from 'dnd-core'
import { SourceType, DragDropManager } from 'dnd-core'
import {
DragSourceSpec,
DragSourceCollector,
Expand All @@ -11,7 +11,7 @@ import checkDecoratorArguments from './utils/checkDecoratorArguments'
import decorateHandler from './decorateHandler'
import registerSource from './registerSource'
import createSourceFactory from './createSourceFactory'
import createSourceMonitor from './createSourceMonitor'
import DragSourceMonitorImpl from './DragSourceMonitorImpl'
import createSourceConnector from './createSourceConnector'
import isValidType from './utils/isValidType'
const invariant = require('invariant')
Expand Down Expand Up @@ -84,7 +84,8 @@ export default function DragSource<Props, CollectedProps = {}, DragObject = {}>(
containerDisplayName: 'DragSource',
createHandler: createSource,
registerHandler: registerSource,
createMonitor: createSourceMonitor,
createMonitor: (manager: DragDropManager<any>) =>
new DragSourceMonitorImpl(manager),
createConnector: createSourceConnector,
DecoratedComponent,
getType,
Expand Down
Expand Up @@ -13,19 +13,19 @@ const invariant = require('invariant')
let isCallingCanDrag = false
let isCallingIsDragging = false

class SourceMonitor implements DragSourceMonitor {
export default class DragSourceMonitorImpl implements DragSourceMonitor {
private internalMonitor: DragDropMonitor
private sourceId: Identifier | undefined
private sourceId: Identifier | null = null

constructor(manager: DragDropManager<any>) {
this.internalMonitor = manager.getMonitor()
}

public receiveHandlerId(sourceId: Identifier) {
public receiveHandlerId(sourceId: Identifier | null) {
this.sourceId = sourceId
}

public getHandlerId(): Identifier | undefined {
public getHandlerId(): Identifier | null {
return this.sourceId
}

Expand Down Expand Up @@ -137,9 +137,3 @@ class SourceMonitor implements DragSourceMonitor {
return this.internalMonitor.getDifferenceFromInitialOffset()
}
}

export default function createSourceMonitor<Context>(
manager: DragDropManager<Context>,
): DragSourceMonitor {
return new SourceMonitor(manager) as DragSourceMonitor
}
7 changes: 4 additions & 3 deletions packages/react-dnd/src/DropTarget.ts
@@ -1,6 +1,6 @@
declare var require: any
import * as React from 'react'
import { TargetType } from 'dnd-core'
import { TargetType, DragDropManager } from 'dnd-core'
import {
DropTargetSpec,
DndOptions,
Expand All @@ -11,9 +11,9 @@ import checkDecoratorArguments from './utils/checkDecoratorArguments'
import decorateHandler from './decorateHandler'
import registerTarget from './registerTarget'
import createTargetFactory from './createTargetFactory'
import createTargetMonitor from './createTargetMonitor'
import createTargetConnector from './createTargetConnector'
import isValidType from './utils/isValidType'
import DropTargetMonitorImpl from './DropTargetMonitorImpl'
const invariant = require('invariant')
const isPlainObject = require('lodash/isPlainObject')

Expand Down Expand Up @@ -77,7 +77,8 @@ export default function DropTarget<Props, CollectedProps = {}>(
containerDisplayName: 'DropTarget',
createHandler: createTarget,
registerHandler: registerTarget,
createMonitor: createTargetMonitor,
createMonitor: (manager: DragDropManager<any>) =>
new DropTargetMonitorImpl(manager),
createConnector: createTargetConnector,
DecoratedComponent,
getType,
Expand Down
Expand Up @@ -11,19 +11,19 @@ const invariant = require('invariant')

let isCallingCanDrop = false

export class TargetMonitor implements DropTargetMonitor {
export default class DropTargetMonitorImpl implements DropTargetMonitor {
private internalMonitor: DragDropMonitor
private targetId: Identifier | undefined
private targetId: Identifier | null = null

constructor(manager: DragDropManager<any>) {
this.internalMonitor = manager.getMonitor()
}

public receiveHandlerId(targetId: Identifier) {
public receiveHandlerId(targetId: Identifier | null) {
this.targetId = targetId
}

public getHandlerId(): Identifier | undefined {
public getHandlerId(): Identifier | null {
return this.targetId
}

Expand Down Expand Up @@ -89,9 +89,3 @@ export class TargetMonitor implements DropTargetMonitor {
return this.internalMonitor.getDifferenceFromInitialOffset()
}
}

export default function createTargetMonitor<Context>(
manager: DragDropManager<Context>,
): DropTargetMonitor {
return new TargetMonitor(manager) as DropTargetMonitor
}
46 changes: 16 additions & 30 deletions packages/react-dnd/src/hooks/useDragSource.ts
@@ -1,17 +1,11 @@
import { useMemo, useEffect } from 'react'
import { useEffect } from 'react'
import { SourceType } from 'dnd-core'
import registerSource from '../registerSource'
import createSourceMonitor from '../createSourceMonitor'
import {
DragSourceMonitor,
DragPreviewOptions,
DragSourceHookSpec,
} from '../interfaces'
import { DragPreviewOptions, DragSourceHookSpec } from '../interfaces'
import { useDragSourceHandler } from './useDragSourceHandler'
import { useDragDropManager } from './useDragDropManager'
import { useMonitorSubscription } from './useMonitorSubscription'
import { Ref, HandlerManager, isRef } from './util'
import { Ref, isRef } from './util'
import { useMonitorOutput } from './useMonitorOutput'
import { useDragSourceMonitor } from './useDragSourceMonitor'

/**
* useDragSource hook (This API is experimental and subject to breaking changes in non-major versions)
Expand All @@ -28,23 +22,15 @@ export function useDragSource<DragObject, CustomProps>(
dragPreviewOptions?: DragPreviewOptions
},
): CustomProps {
const dragDropManager = useDragDropManager()
const backend = dragDropManager.getBackend()
const sourceMonitor = useMemo(() => createSourceMonitor(dragDropManager), [
dragDropManager,
]) as DragSourceMonitor & HandlerManager

const manager = useDragDropManager()
const backend = manager.getBackend()
const handler = useDragSourceHandler<DragObject, CustomProps>(sourceSpec)
const sourceMonitor = useDragSourceMonitor(type, handler, manager)

useMonitorSubscription(
registerSource,
type,
handler,
dragDropManager,
sourceMonitor,
)

useEffect(function connectDragSourceToBackend() {
/*
* Connect the Drag Source Element to the Backend
*/
useEffect(function connectDragSource() {
const dragSourceNode = ref.current
const dragSourceOptions = sourceSpec.dragSourceOptions
return backend.connectDragSource(
Expand All @@ -54,17 +40,17 @@ export function useDragSource<DragObject, CustomProps>(
)
}, [])

useEffect(function connectDragPreviewToBackend() {
/*
* Connect the Drag Previem Element to the Backend
*/
useEffect(function connectDragPreview() {
if (sourceSpec.dragPreview == null) {
return undefined
}

// Accept ref or dom node
const dragPreviewNode = isRef(sourceSpec.dragPreview)
? (sourceSpec.dragPreview as Ref<any>).current
: sourceSpec.dragPreview

const dragPreviewOptions = sourceSpec.dragPreviewOptions
const { dragPreviewOptions } = sourceSpec
return backend.connectDragPreview(
sourceMonitor.getHandlerId(),
dragPreviewNode,
Expand Down

0 comments on commit 7f78a8b

Please sign in to comment.