Skip to content

Commit

Permalink
Merge pull request #750 from clauderic/dnd-monitor-refactor
Browse files Browse the repository at this point in the history
Refactor `useDndMonitor()` to dispatch events synchronously
  • Loading branch information
clauderic authored May 20, 2022
2 parents c329438 + bf30718 commit 01f0a2d
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 105 deletions.
7 changes: 7 additions & 0 deletions .changeset/dnd-monitor-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@dnd-kit/core': minor
---

The `useDndMonitor()` hook has been refactored to be synchronously invoked at the same time as the events dispatched by `<DndContext>` (such as `onDragStart`, `onDragOver`, `onDragEnd`).

The new refactor uses the subscribe/notify pattern and no longer causes re-renders in consuming components of `useDndMonitor()` when events are dispatched.
4 changes: 2 additions & 2 deletions packages/core/src/components/Accessibility/Accessibility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {createPortal} from 'react-dom';
import {useUniqueId} from '@dnd-kit/utilities';
import {HiddenText, LiveRegion, useAnnouncement} from '@dnd-kit/accessibility';

import {DndMonitorArguments, useDndMonitor} from '../../hooks/monitor';
import type {UniqueIdentifier} from '../../types';
import {DndMonitorListener, useDndMonitor} from '../DndMonitor';

import type {Announcements, ScreenReaderInstructions} from './types';
import {
Expand Down Expand Up @@ -34,7 +34,7 @@ export function Accessibility({
}, []);

useDndMonitor(
useMemo<DndMonitorArguments>(
useMemo<DndMonitorListener>(
() => ({
onDragStart({active}) {
announce(announcements.onDragStart(active.id));
Expand Down
32 changes: 16 additions & 16 deletions packages/core/src/components/DndContext/DndContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
getInitialState,
reducer,
} from '../../store';
import {DndMonitorContext, DndMonitorState} from '../../hooks/monitor';
import {DndMonitorContext, useDndMonitorProvider} from '../DndMonitor';
import {
useAutoScroller,
useCachedNode,
Expand Down Expand Up @@ -144,10 +144,10 @@ export const DndContext = memo(function DndContext({
}: Props) {
const store = useReducer(reducer, undefined, getInitialState);
const [state, dispatch] = store;
const [monitorState, setMonitorState] = useState<DndMonitorState>(() => ({
type: null,
event: null,
}));
const [
dispatchMonitorEvent,
registerMonitorListener,
] = useDndMonitorProvider();
const [status, setStatus] = useState<Status>(Status.Uninitialized);
const isInitialized = status === Status.Initialized;
const {
Expand Down Expand Up @@ -375,7 +375,7 @@ export const DndContext = memo(function DndContext({
initialCoordinates,
active: id,
});
setMonitorState({type: Action.DragStart, event});
dispatchMonitorEvent({type: 'onDragStart', event});
});
},
onMove(coordinates) {
Expand Down Expand Up @@ -432,16 +432,14 @@ export const DndContext = memo(function DndContext({
setActiveSensor(null);
setActivatorEvent(null);

if (event) {
setMonitorState({type, event});
}
const eventName =
type === Action.DragEnd ? 'onDragEnd' : 'onDragCancel';

if (event) {
const {onDragCancel, onDragEnd} = latestProps.current;
const handler =
type === Action.DragEnd ? onDragEnd : onDragCancel;
const handler = latestProps.current[eventName];

handler?.(event);
dispatchMonitorEvent({type: eventName, event});
}
});
};
Expand Down Expand Up @@ -527,8 +525,10 @@ export const DndContext = memo(function DndContext({
over,
};

setMonitorState({type: Action.DragMove, event});
onDragMove?.(event);
unstable_batchedUpdates(() => {
onDragMove?.(event);
dispatchMonitorEvent({type: 'onDragMove', event});
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[scrollAdjustedTranslate.x, scrollAdjustedTranslate.y]
Expand Down Expand Up @@ -577,8 +577,8 @@ export const DndContext = memo(function DndContext({

unstable_batchedUpdates(() => {
setOver(over);
setMonitorState({type: Action.DragOver, event});
onDragOver?.(event);
dispatchMonitorEvent({type: 'onDragOver', event});
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -701,7 +701,7 @@ export const DndContext = memo(function DndContext({
]);

return (
<DndMonitorContext.Provider value={monitorState}>
<DndMonitorContext.Provider value={registerMonitorListener}>
<InternalContext.Provider value={internalContext}>
<PublicContext.Provider value={publicContext}>
<ActiveDraggableContext.Provider value={transform}>
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/components/DndMonitor/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {createContext} from 'react';

import type {RegisterListener} from './types';

export const DndMonitorContext = createContext<RegisterListener | null>(null);
4 changes: 4 additions & 0 deletions packages/core/src/components/DndMonitor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {DndMonitorContext} from './context';
export type {DndMonitorListener, DndMonitorEvent} from './types';
export {useDndMonitor} from './useDndMonitor';
export {useDndMonitorProvider} from './useDndMonitorProvider';
31 changes: 31 additions & 0 deletions packages/core/src/components/DndMonitor/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type {
DragStartEvent,
DragCancelEvent,
DragEndEvent,
DragMoveEvent,
DragOverEvent,
} from '../../types';

export interface DndMonitorListener {
onDragStart?(event: DragStartEvent): void;
onDragMove?(event: DragMoveEvent): void;
onDragOver?(event: DragOverEvent): void;
onDragEnd?(event: DragEndEvent): void;
onDragCancel?(event: DragCancelEvent): void;
}

export interface DndMonitorEvent {
type: keyof DndMonitorListener;
event:
| DragStartEvent
| DragMoveEvent
| DragOverEvent
| DragEndEvent
| DragCancelEvent;
}

export type UnregisterListener = () => void;

export type RegisterListener = (
listener: DndMonitorListener
) => UnregisterListener;
20 changes: 20 additions & 0 deletions packages/core/src/components/DndMonitor/useDndMonitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {useContext, useEffect} from 'react';

import {DndMonitorContext} from './context';
import type {DndMonitorListener} from './types';

export function useDndMonitor(listener: DndMonitorListener) {
const registerListener = useContext(DndMonitorContext);

useEffect(() => {
if (!registerListener) {
throw new Error(
'useDndMonitor must be used within a children of <DndContext>'
);
}

const unsubscribe = registerListener(listener);

return unsubscribe;
}, [listener, registerListener]);
}
24 changes: 24 additions & 0 deletions packages/core/src/components/DndMonitor/useDndMonitorProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {useCallback, useState} from 'react';

import type {DndMonitorListener, DndMonitorEvent} from './types';

export function useDndMonitorProvider() {
const [listeners] = useState(() => new Set<DndMonitorListener>());

const registerListener = useCallback(
(listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
},
[listeners]
);

const dispatch = useCallback(
({type, event}: DndMonitorEvent) => {
listeners.forEach((listener) => listener[type]?.(event as any));
},
[listeners]
);

return [dispatch, registerListener] as const;
}
2 changes: 2 additions & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type {
DraggableMeasuring,
MeasuringConfiguration,
} from './DndContext';
export {useDndMonitor} from './DndMonitor';
export type {DndMonitorListener} from './DndMonitor';
export {
DragOverlay,
defaultDropAnimation,
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export {useDndMonitor} from './monitor';
export type {DndMonitorArguments} from './monitor';
export {useDraggable} from './useDraggable';
export type {
DraggableAttributes,
Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/hooks/monitor/index.ts

This file was deleted.

78 changes: 0 additions & 78 deletions packages/core/src/hooks/monitor/useDndMonitor.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ export {
defaultScreenReaderInstructions,
defaultDropAnimation,
defaultDropAnimationSideEffects,
useDndMonitor,
} from './components';
export type {
Announcements,
CancelDrop,
DndContextProps,
DndMonitorListener,
DndMonitorListener as DndMonitorArguments,
DragOverlayProps,
DropAnimation,
DropAnimationFunction,
Expand All @@ -28,12 +31,10 @@ export {
TraversalOrder,
useDraggable,
useDndContext,
useDndMonitor,
useDroppable,
} from './hooks';
export type {
AutoScrollOptions,
DndMonitorArguments,
DraggableAttributes,
DraggableSyntheticListeners,
DroppableMeasuring,
Expand Down

0 comments on commit 01f0a2d

Please sign in to comment.