question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Performance issues with useDragLayer hook

See original GitHub issue

Describe the bug I am animating a hand of playing cards. I translate each card individually when it is dragged (instead of having a drag preview), therefore I have 1 custom drag layer per card. The performances was dropping with the number of cards in hand, so I tried to avoid listening to the client offset in each drag layer. It turn out it is not possible given the current implementation of useDragLayer hook.

The 2 “useEffect” hooks inside useDragLayer looks pretty strange to me: why would you want to subscribe to something every time your component rerenders? It turns out that inside the monitor, we add a listener to the redux store each time.

I tried to reimplement useDragLayer but with only a single subscription when the hook is created: it does not work. I could not understand why the listener of the redux store is never called when I did that.

Expected behavior I want to be able to collect the offset values conditionally in useDragLayer hook, in order to have multiple hooks but only one which causes the component to rerender. Also, I think the spamming of listener into the redux store should be fixed to improve the performances.

Workaround I created a custom specialized hook which performs much better:

const useDragOffsetDiff = (enabled: Boolean) => {
  const {dragDropManager} = useContext(DndContext)
  invariant(dragDropManager != null, 'Expected drag drop context')
  const monitor = (dragDropManager as DragDropManager).getMonitor()
  const [dragOffsetDiff, setDragOffsetDiff] = useState(monitor.getDifferenceFromInitialOffset())
  const offsetChangeListener = () => setDragOffsetDiff(monitor.getDifferenceFromInitialOffset())
  const [intervalId, setIntervalId] = useState<NodeJS.Timeout>()
  useEffect(() => {
    if (enabled) {
      setIntervalId(setInterval(offsetChangeListener, 40))
    } else if (intervalId) {
      clearInterval(intervalId)
    }
  }, [enabled])
  return dragOffsetDiff
}

Right now I could not find another way than setInterval to collect the offset. The store is not exposed by the DragDropManager, so I could not add my own listener to it.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:1
  • Comments:15 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
fromicommented, Nov 20, 2020

I just came up with a better workaround, by listening to the redux store instead of setting an interval:

import {invariant} from '@react-dnd/invariant'
import {DragDropManager} from 'dnd-core/lib/interfaces'
import {useContext, useEffect, useState} from 'react'
import {DndContext} from 'react-dnd'

export const useDragOffsetDiff = (enabled: Boolean, fps = 60) => {
  const {dragDropManager} = useContext(DndContext)
  invariant(dragDropManager != null, 'Expected drag drop context')
  const monitor = (dragDropManager as DragDropManager).getMonitor()
  const [dragOffsetDiff, setDragOffsetDiff] = useState(monitor.getDifferenceFromInitialOffset())
  const offsetChangeListener = () => setDragOffsetDiff(monitor.getDifferenceFromInitialOffset())
  const [intervalId, setIntervalId] = useState<NodeJS.Timeout>()
  const cleanup = () => {
    if (intervalId) {
      clearInterval(intervalId)
      setDragOffsetDiff({x: 0, y: 0})
    }
  }
  useEffect(() => {
    if (enabled) {
      setIntervalId(setInterval(() => {
        offsetChangeListener()
      }, 1000 / fps))
    } else {
      cleanup()
    }
    return cleanup
  }, [enabled])
  return dragOffsetDiff
}
3reactions
fromicommented, Mar 31, 2021

I just came with a far better solution. This is how I use “useDragLayer” and prevent it from spamming too many changes:

import {shallowEqual} from '@react-dnd/shallowequal'
import {useState} from 'react'
import {DragLayerMonitor, useDragLayer} from 'react-dnd'

export default function useEfficientDragLayer<CollectedProps>(collect: (monitor: DragLayerMonitor) => CollectedProps): CollectedProps {
  const collected = useDragLayer(collect)
  const [previousCollected, setPreviousCollected] = useState<CollectedProps>(collected)
  const [requestID, setRequestID] = useState<number>()
  if (requestID === undefined && !shallowEqual(collected, previousCollected)) {
    setPreviousCollected(collected)
    setRequestID(requestAnimationFrame(() => setRequestID(undefined)))
  }
  return previousCollected
}

A few examples of drag&drop using this hook are available on my website: https://game-park.com/

Read more comments on GitHub >

github_iconTop Results From Across the Web

Solving common problems, performance and scalability ...
Solving common problems, performance and scalability issues with react hooks. I will cover most commonly used hooks: useState; useEffect; useReduce ...
Read more >
useDragLayer - React DnD
The useDragLayer hook allows you to wire a component into the DnD system as a drag layer. import { useDragLayer } from 'react-dnd'...
Read more >
React Dnd Too Many Rerenders With Custom Drag Layer ...
The useDragLayer hook allows you to wire a component into the DnD system as a drag layer. import ... Performance issues with useDragLayer...
Read more >
React DnD performances with many drop targets
I guess this problem comes from the fact that when I start dragging, the DnD layer searches for all the components that are...
Read more >
React Hooks: Performance Pitfalls And How To Easily Avoid ...
Re-Renders Matter. Alright, we identified that we may encounter some performance issues while using Hooks, but where are they coming from?
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found