From 32b0bbfff4b65662487ecbaadd5b41bfe6884534 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Mon, 28 Mar 2022 20:19:19 -0400 Subject: [PATCH] fix: "Invariant Violation: Expected targetIds to be registered." (#3409) * fix: "Invariant Violation: Expected targetIds to be registered." We've sporadically seen this error from customers. The call stack indicates that it's originating from HTML5BackendImpl's handleTopDragOver's requestAnimationFrame callback. I've been unable to reproduce it locally; however, if I simulate a slowdown by replacing `requestAnimationFrame(callback)` with `setTimeout(callback, 10000)`, I can fairly reliably reproduce this error. To fix it, I believe HTML5BackendImpl should consistently clear the hover animation whenever the drag operation is ended for any reason. I locally tested this fix in the v15.1.2 tag, and it appeared to work. Fixes #763, #3403 * chore: semver * fix: cancel any raf before creating a new one Co-authored-by: Chris Trevino --- .github/workflows/ci.yml | 6 +-- .github/workflows/version-check.yml | 16 ++++++ .yarn/versions/e42d7e63.yml | 9 ++++ CONTRIBUTING.md | 2 +- .../backend-html5/src/HTML5BackendImpl.ts | 50 +++++++++++++------ 5 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/version-check.yml create mode 100644 .yarn/versions/e42d7e63.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb71b2d159..72a71f8359 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,7 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - - - run: yarn version check - if: "github.actor != 'dependabot[bot]' && !contains(github.ref , 'release/') && !contains(github.head_ref , 'release/')" - name: Version Check - + - run: yarn install name: Install Dependencies diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml new file mode 100644 index 0000000000..46d146966c --- /dev/null +++ b/.github/workflows/version-check.yml @@ -0,0 +1,16 @@ +name: Version Check +on: [push, pull_request] +env: + DEFAULT_NODE_VERSION: 16 +jobs: + version-check: + runs-on: ubuntu-latest + if: "github.ref != 'main' && github.head_ref != 'main' && !contains(github.ref , 'release/') && !contains(github.head_ref , 'release/') && !contains(github.ref , 'dependabot/') && !contains(github.head_ref , 'dependabot/')" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-node@v1 + with: + node-version: ${{env.DEFAULT_NODE_VERSION}} + - run: yarn version check diff --git a/.yarn/versions/e42d7e63.yml b/.yarn/versions/e42d7e63.yml new file mode 100644 index 0000000000..4ea38cde91 --- /dev/null +++ b/.yarn/versions/e42d7e63.yml @@ -0,0 +1,9 @@ +releases: + react-dnd-html5-backend: patch + +declined: + - react-dnd-documentation + - react-dnd-examples + - test-suite-cra + - test-suite-vite + - react-dnd-test-utils diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbc62f4abe..c4319f133e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to React-DnD -So you want to contibute to React-DnD? Thank you! This library is a community effort, and your contributions are greatly appreciated. +So you want to contribute to React-DnD? Thank you! This library is a community effort, and your contributions are greatly appreciated. ## FAQ diff --git a/packages/backend-html5/src/HTML5BackendImpl.ts b/packages/backend-html5/src/HTML5BackendImpl.ts index 9cd8519edc..f509841aba 100644 --- a/packages/backend-html5/src/HTML5BackendImpl.ts +++ b/packages/backend-html5/src/HTML5BackendImpl.ts @@ -347,6 +347,7 @@ export class HTML5BackendImpl implements Backend { if (this.clearCurrentDragSourceNode() && this.monitor.isDragging()) { this.actions.endDrag() } + this.cancelHover() } private setCurrentDragSourceNode(node: Element | null) { @@ -400,6 +401,36 @@ export class HTML5BackendImpl implements Backend { return false } + private scheduleHover = (dragOverTargetIds: string[] | null) => { + if ( + this.hoverRafId === null && + typeof requestAnimationFrame !== 'undefined' + ) { + // cancel any existing hover if present + this.cancelHover() + + this.hoverRafId = requestAnimationFrame(() => { + if (this.monitor.isDragging()) { + this.actions.hover(dragOverTargetIds || [], { + clientOffset: this.lastClientOffset, + }) + } + + this.hoverRafId = null + }) + } + } + + private cancelHover = () => { + if ( + this.hoverRafId !== null && + typeof cancelAnimationFrame !== 'undefined' + ) { + cancelAnimationFrame(this.hoverRafId) + this.hoverRafId = null + } + } + public handleTopDragStartCapture = (): void => { this.clearCurrentDragSourceNode() this.dragStartSourceIds = [] @@ -429,6 +460,7 @@ export class HTML5BackendImpl implements Backend { // Avoid crashing if we missed a drop event or our previous drag died if (this.monitor.isDragging()) { this.actions.endDrag() + this.cancelHover() } // Don't publish the source just yet (see why below) @@ -530,6 +562,7 @@ export class HTML5BackendImpl implements Backend { // Only proceed if we have not handled it already. this.actions.endDrag() } + this.cancelHover() } public handleTopDragEnterCapture = (e: DragEvent): void => { @@ -622,20 +655,7 @@ export class HTML5BackendImpl implements Backend { this.altKeyPressed = e.altKey this.lastClientOffset = getEventClientOffset(e) - if ( - this.hoverRafId === null && - typeof requestAnimationFrame !== 'undefined' - ) { - this.hoverRafId = requestAnimationFrame(() => { - if (this.monitor.isDragging()) { - this.actions.hover(dragOverTargetIds || [], { - clientOffset: this.lastClientOffset, - }) - } - - this.hoverRafId = null - }) - } + this.scheduleHover(dragOverTargetIds) const canDrop = (dragOverTargetIds || []).some((targetId) => this.monitor.canDropOnTarget(targetId), @@ -672,6 +692,7 @@ export class HTML5BackendImpl implements Backend { if (this.isDraggingNativeItem()) { setTimeout(() => this.endDragNativeItem(), 0) } + this.cancelHover() } public handleTopDropCapture = (e: DragEvent): void => { @@ -709,6 +730,7 @@ export class HTML5BackendImpl implements Backend { } else if (this.monitor.isDragging()) { this.actions.endDrag() } + this.cancelHover() } public handleSelectStart = (e: DragEvent): void => {