Skip to content

Commit

Permalink
Task/use layout effects for registration (#1437)
Browse files Browse the repository at this point in the history
* fix: use useLayoutEffect for item registration

* fix: use layout effects when attaching to dnd-core

bring in changes from #1429

* docs: add test case based on codesandbox sample in reported issue
  • Loading branch information
darthtrevino committed Jul 3, 2019
1 parent ce58e73 commit a1fbcd2
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 12 deletions.
4 changes: 2 additions & 2 deletions packages/core/react-dnd/src/hooks/internal/drag.ts
@@ -1,4 +1,4 @@
import { useEffect, useMemo, MutableRefObject } from 'react'
import { useMemo, MutableRefObject, useLayoutEffect } from 'react'
import invariant from 'invariant'
import {
DragSourceHookSpec,
Expand Down Expand Up @@ -71,7 +71,7 @@ export function useDragHandler<
} as DragSource
}, [])

useEffect(function registerHandler() {
useLayoutEffect(function registerHandler() {
const [handlerId, unregister] = registerSource(
spec.current.item.type,
handler,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/react-dnd/src/hooks/internal/drop.ts
Expand Up @@ -3,7 +3,7 @@ import {
DropTargetMonitor,
DropTargetHookSpec,
} from '../../interfaces'
import { useEffect, useMemo, MutableRefObject } from 'react'
import { useMemo, MutableRefObject, useLayoutEffect } from 'react'
import { DropTarget } from 'dnd-core'
import { registerTarget } from '../../common/registration'
import { useDragDropManager } from './useDragDropManager'
Expand Down Expand Up @@ -52,7 +52,7 @@ export function useDropHandler<
} as DropTarget
}, [monitor])

useEffect(
useLayoutEffect(
function registerHandler() {
const [handlerId, unregister] = registerTarget(
spec.current.accept,
Expand Down
8 changes: 6 additions & 2 deletions packages/core/react-dnd/src/hooks/internal/useCollector.ts
@@ -1,9 +1,9 @@
import shallowEqual from 'shallowequal'
import { useState, useCallback } from 'react'
import { useState, useCallback, useLayoutEffect } from 'react'

/**
*
* @param monitor The monitor to colelct state from
* @param monitor The monitor to collect state from
* @param collect The collecting function
* @param onUpdate A method to invoke when updates occur
*/
Expand All @@ -24,5 +24,9 @@ export function useCollector<T, S>(
}
}, [collected, monitor, onUpdate])

// update the collected properties after the first render
// and the components are attached to dnd-core
useLayoutEffect(updateCollected, [])

return [collected, updateCollected]
}
6 changes: 3 additions & 3 deletions packages/core/react-dnd/src/hooks/useDrag.ts
@@ -1,4 +1,4 @@
import { useEffect, useRef, useMemo } from 'react'
import { useRef, useMemo, useLayoutEffect } from 'react'
import invariant from 'invariant'
import {
DragSourceHookSpec,
Expand Down Expand Up @@ -42,11 +42,11 @@ export function useDrag<
const connectDragPreview = useMemo(() => connector.hooks.dragPreview(), [
connector,
])
useEffect(() => {
useLayoutEffect(() => {
connector.dragSourceOptions = specRef.current.options || null
connector.reconnect()
}, [connector])
useEffect(() => {
useLayoutEffect(() => {
connector.dragPreviewOptions = specRef.current.previewOptions || null
connector.reconnect()
}, [connector])
Expand Down
4 changes: 2 additions & 2 deletions packages/core/react-dnd/src/hooks/useDrop.ts
@@ -1,4 +1,4 @@
import { useEffect, useRef, useMemo } from 'react'
import { useRef, useMemo, useLayoutEffect } from 'react'
import invariant from 'invariant'
import {
DropTargetHookSpec,
Expand Down Expand Up @@ -36,7 +36,7 @@ export function useDrop<
connector,
])

useEffect(() => {
useLayoutEffect(() => {
connector.dropTargetOptions = spec.options || null
connector.reconnect()
}, [spec.options])
Expand Down
@@ -0,0 +1,9 @@
---
path: '/examples/other/remount-with-correct-props'
title: 'Drag Source Remounts with Correct Props'
---

Regression example based on [#1429](https://github.com/react-dnd/react-dnd/pull/1429) that verifies that as drag-sources are remounted, they receive their correct props from React-DnD.

<view-source name="06-other/remount-with-correct-props" component="other-remount-with-correct-props">
</view-source>
4 changes: 4 additions & 0 deletions packages/documentation/docsite/src/constants.ts
Expand Up @@ -242,6 +242,10 @@ export const ExamplePages: PageGroup[] = [
location: '/examples/other/drag-source-rerender',
title: 'Drag Source Rerender',
},
OTHER_REMOUNT_WITH_CORRECT_PROPS: {
location: '/examples/other/remount-with-correct-props',
title: 'Drag Source Remount',
},
OTHER_NATIVE_FILES: {
location: '/examples/other/native-files',
title: 'Native Files',
Expand Down
@@ -0,0 +1,3 @@
export default {
BOX: 'box',
}
@@ -0,0 +1,45 @@
import React from 'react'
import { DragSource, ConnectDragSource } from 'react-dnd'
import ItemTypes from './ItemTypes'

const getStyle = (isDragging: boolean) => ({
display: 'inline-block',
padding: '0.5rem 1rem',
cursor: 'pointer',
border: '1px dashed gray',
backgroundColor: isDragging ? 'red' : undefined,
opacity: isDragging ? 0.4 : 1,
})

export interface SourceBoxProps {
id: string
isDragging: boolean
connectDragSource: ConnectDragSource
onBeginDrag: () => void
onEndDrag: () => void
}
const SourceBox: React.FC<SourceBoxProps> = ({
isDragging,
connectDragSource,
}) => {
return connectDragSource(<div style={getStyle(isDragging)}>Drag me</div>)
}

export default DragSource(
ItemTypes.BOX,
{
beginDrag: (props: SourceBoxProps) => {
props.onBeginDrag()
return { id: props.id }
},
endDrag: (props: SourceBoxProps) => {
props.onEndDrag()
},
isDragging: (props: SourceBoxProps, monitor) =>
monitor.getItem().id === props.id,
},
(connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}),
)(SourceBox)
@@ -0,0 +1,31 @@
import React from 'react'
import { DropTarget, ConnectDropTarget } from 'react-dnd'
import ItemTypes from './ItemTypes'

const style: React.CSSProperties = {
border: '1px solid gray',
height: '15rem',
width: '15rem',
padding: '2rem',
textAlign: 'center',
}

export interface TargetBoxProps {
connectDropTarget: ConnectDropTarget
isActive: boolean
}
const TargetBox: React.FC<TargetBoxProps> = ({
connectDropTarget,
isActive,
}) => {
return connectDropTarget(
<div style={style}>{isActive ? 'Release to drop' : 'Drag item here'}</div>,
)
}

export default DropTarget(ItemTypes.BOX, {}, (connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
isActive: monitor.isOver() && monitor.canDrop(),
}
})(TargetBox)
@@ -0,0 +1,43 @@
import React, { useState, useEffect, useCallback } from 'react'
import SourceBox from './SourceBox'
import TargetBox from './TargetBox'

export default function Container() {
const [isMounted, setIsMounted] = useState(true)
const [isDragging, setIsDragging] = useState(false)
useEffect(
function subscribeToIntervalTick() {
if (!isDragging) {
setIsMounted(true)
return
}
const interval = setInterval(() => setIsMounted(!isMounted), 500)
return () => clearInterval(interval)
},
[isMounted, isDragging],
)
const handleBeginDrag = useCallback(() => setIsDragging(true), [])
const handleEndDrag = useCallback(() => setIsDragging(false), [])

return (
<>
<div style={{ height: 100 }}>
<div style={{ marginTop: 10 }}>
{isMounted && (
<SourceBox
id="1"
onBeginDrag={handleBeginDrag}
onEndDrag={handleEndDrag}
/>
)}
</div>
</div>
<div>
<div>Target</div>
<div style={{ marginTop: 10 }}>
<TargetBox />
</div>
</div>
</>
)
}
4 changes: 3 additions & 1 deletion packages/documentation/examples-decorators/src/index.ts
Expand Up @@ -15,6 +15,7 @@ import sortableStressTest from './04-sortable/stress-test'
import customizeDropEffects from './05-customize/drop-effects'
import customizeHandlesAndPreviews from './05-customize/handles-and-previews'
import dragSourceRerender from './06-other/drag-source-rerender'
import remountWithCorrectProps from './06-other/remount-with-correct-props'
import otherNativeFiles from './06-other/native-files'

export * from './isDebugMode'
Expand All @@ -31,11 +32,12 @@ export const componentIndex: {
'drag-around-naive': dragAroundNaive,
'nesting-drag-sources': nestingDragSources,
'nesting-drop-targets': nestingDropTargets,
'sortable-cancel-on-drop-outside': sortableCancelOnDropOutside as any,
'sortable-cancel-on-drop-outside': sortableCancelOnDropOutside,
'sortable-simple': sortableSimple,
'sortable-stress-test': sortableStressTest,
'customize-drop-effects': customizeDropEffects,
'customize-handles-and-previews': customizeHandlesAndPreviews,
'other-drag-source-rerender': dragSourceRerender,
'other-remount-with-correct-props': remountWithCorrectProps,
'other-native-files': otherNativeFiles,
}
@@ -0,0 +1,3 @@
export default {
BOX: 'box',
}
@@ -0,0 +1,37 @@
import React from 'react'
import { useDrag } from 'react-dnd'
import ItemTypes from './ItemTypes'

const getStyle = (isDragging: boolean) => ({
display: 'inline-block',
padding: '0.5rem 1rem',
cursor: 'pointer',
border: '1px dashed gray',
backgroundColor: isDragging ? 'red' : undefined,
opacity: isDragging ? 0.4 : 1,
})

export interface SourceBoxProps {
id: string
onBeginDrag: () => void
onEndDrag: () => void
}
export const SourceBox: React.FC<SourceBoxProps> = ({
id,
onBeginDrag,
onEndDrag,
}) => {
const [{ isDragging }, drag] = useDrag({
item: { type: ItemTypes.BOX, id },
isDragging: monitor => monitor.getItem().id === id,
collect: monitor => ({ isDragging: monitor.isDragging() }),
begin: onBeginDrag,
end: onEndDrag,
})

return (
<div ref={drag} style={getStyle(isDragging)}>
Drag me
</div>
)
}
@@ -0,0 +1,27 @@
import React from 'react'
import { useDrop } from 'react-dnd'
import ItemTypes from './ItemTypes'

const style: React.CSSProperties = {
border: '1px solid gray',
height: '15rem',
width: '15rem',
padding: '2rem',
textAlign: 'center',
}

const TargetBox: React.FC = () => {
const [{ isActive }, drop] = useDrop({
accept: ItemTypes.BOX,
collect: monitor => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
})
return (
<div ref={drop} style={style}>
{isActive ? 'Release to drop' : 'Drag item here'}
</div>
)
}

export default TargetBox
@@ -0,0 +1,43 @@
import React, { useState, useEffect, useCallback } from 'react'
import { SourceBox } from './SourceBox'
import TargetBox from './TargetBox'

export default function Container() {
const [isMounted, setIsMounted] = useState(true)
const [isDragging, setIsDragging] = useState(false)
useEffect(
function subscribeToIntervalTick() {
if (!isDragging) {
setIsMounted(true)
return
}
const interval = setInterval(() => setIsMounted(!isMounted), 500)
return () => clearInterval(interval)
},
[isMounted, isDragging],
)
const handleBeginDrag = useCallback(() => setIsDragging(true), [])
const handleEndDrag = useCallback(() => setIsDragging(false), [])

return (
<>
<div style={{ height: 100 }}>
<div style={{ marginTop: 10 }}>
{isMounted && (
<SourceBox
id="1"
onBeginDrag={handleBeginDrag}
onEndDrag={handleEndDrag}
/>
)}
</div>
</div>
<div>
<div>Target</div>
<div style={{ marginTop: 10 }}>
<TargetBox />
</div>
</div>
</>
)
}
2 changes: 2 additions & 0 deletions packages/documentation/examples-hooks/src/index.ts
Expand Up @@ -15,6 +15,7 @@ import sortableStressTest from './04-sortable/stress-test'
import customizeDropEffects from './05-customize/drop-effects'
import customizeHandlesAndPreviews from './05-customize/handles-and-previews'
import dragSourceRerender from './06-other/drag-source-rerender'
import remountWithCorrectProps from './06-other/remount-with-correct-props'
import otherNativeFiles from './06-other/native-files'

export * from './isDebugMode'
Expand All @@ -37,5 +38,6 @@ export const componentIndex: {
'customize-drop-effects': customizeDropEffects,
'customize-handles-and-previews': customizeHandlesAndPreviews,
'other-drag-source-rerender': dragSourceRerender,
'other-remount-with-correct-props': remountWithCorrectProps,
'other-native-files': otherNativeFiles,
}

0 comments on commit a1fbcd2

Please sign in to comment.