1- import React , { useCallback , useMemo , useRef , useState } from 'react'
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react'
22import { type DragSourceType , type DropTargetConfig , createDropTarget } from '../core'
33import type { DroppableConfig , Kind } from './types'
44import { getDropTargets } from './utils'
@@ -12,11 +12,20 @@ type HoveredData = {
1212
1313export function useDroppable ( config : DroppableConfig ) {
1414 const [ hovered , setHovered ] = useState < HoveredData | null > ( null )
15+ const cleanupRef = useRef < ( ( ) => void ) | null > ( null )
16+ const previousAcceptsRef = useRef ( config . accepts )
1517
1618 let { accepts } = config
1719
1820 const trueAccepts = Array . isArray ( accepts ) || typeof accepts === 'function' ? accepts : [ accepts ]
1921
22+ // Detect if accepts has changed
23+ const acceptsChanged = useMemo ( ( ) => {
24+ const hasChanged = previousAcceptsRef . current !== accepts
25+ previousAcceptsRef . current = accepts
26+ return hasChanged
27+ } , [ accepts ] )
28+
2029 const trueConfig : DropTargetConfig < any > = {
2130 disabled : config . disabled ,
2231 accepts : trueAccepts as unknown as DragSourceType < any > [ ] ,
@@ -74,35 +83,65 @@ export function useDroppable(config: DroppableConfig) {
7483 } ,
7584 }
7685
86+ // Create dropTarget only once
7787 const dropTarget = useMemo ( ( ) => createDropTarget ( trueConfig ) , [ ] )
7888
79- dropTarget . setConfig ( trueConfig )
89+ // Update config when it changes
90+ useEffect ( ( ) => {
91+ dropTarget . setConfig ( trueConfig )
92+ } , [
93+ dropTarget ,
94+ config . disabled ,
95+ config . data ,
96+ config . onDragIn ,
97+ config . onDragOut ,
98+ config . onDragMove ,
99+ config . onDrop ,
100+ acceptsChanged , // Use the memoized value that indicates if accepts changed
101+ ] )
80102
81103 const originalRef = useRef ( null as any )
82104
83- const dropComponentRef = useCallback ( ( element : HTMLElement | null ) => {
84- if ( element ) {
85- dropTarget . listen ( element )
86- }
105+ const dropComponentRef = useCallback (
106+ ( element : HTMLElement | null ) => {
107+ if ( element ) {
108+ // Store the cleanup function
109+ cleanupRef . current = dropTarget . listen ( element )
110+ }
87111
88- const ref = originalRef . current
112+ const ref = originalRef . current
89113
90- if ( typeof ref === 'function' ) {
91- ref ( element )
92- } else if ( ref && ref . hasOwnProperty ( 'current' ) ) {
93- ref . current = element
94- }
95- } , [ ] )
114+ if ( typeof ref === 'function' ) {
115+ ref ( element )
116+ } else if ( ref && ref . hasOwnProperty ( 'current' ) ) {
117+ ref . current = element
118+ }
119+ } ,
120+ [ dropTarget ] ,
121+ )
96122
97- const droppable = useCallback ( ( child : React . ReactElement < React . ComponentPropsWithRef < any > > | null ) => {
98- if ( ! child ) {
99- return null
100- }
123+ const droppable = useCallback (
124+ ( child : React . ReactElement < React . ComponentPropsWithRef < any > > | null ) => {
125+ if ( ! child ) {
126+ return null
127+ }
101128
102- // @ts -ignore React 16-19+ refs compatibility.
103- originalRef . current = child . props ?. ref ?? child . ref
129+ // @ts -ignore React 16-19+ refs compatibility.
130+ originalRef . current = child . props ?. ref ?? child . ref
104131
105- return React . cloneElement ( child , { ref : dropComponentRef } )
132+ return React . cloneElement ( child , { ref : dropComponentRef } )
133+ } ,
134+ [ dropComponentRef ] ,
135+ )
136+
137+ // Clean up all listeners when component unmounts
138+ useEffect ( ( ) => {
139+ return ( ) => {
140+ if ( cleanupRef . current ) {
141+ cleanupRef . current ( )
142+ cleanupRef . current = null
143+ }
144+ }
106145 } , [ ] )
107146
108147 return {
0 commit comments