or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-provider.mddrag-layer.mddrag-sources.mddrop-targets.mdindex.mdutilities.md

drop-targets.mddocs/

0

# Drop Targets

1

2

The useDrop hook enables components to act as drop targets, accepting dragged items and handling drop operations.

3

4

## Capabilities

5

6

### useDrop Hook

7

8

Creates a drop target that can accept dragged items with flexible handling options.

9

10

```typescript { .api }

11

/**

12

* Hook for making components accept dropped items

13

* @param specArg - Drop target specification (object or function)

14

* @param deps - Optional dependency array for memoization

15

* @returns Tuple of [collected props, drop ref connector]

16

*/

17

function useDrop<DragObject = unknown, DropResult = unknown, CollectedProps = unknown>(

18

specArg: FactoryOrInstance<DropTargetHookSpec<DragObject, DropResult, CollectedProps>>,

19

deps?: unknown[]

20

): [CollectedProps, ConnectDropTarget];

21

22

interface DropTargetHookSpec<DragObject, DropResult, CollectedProps> {

23

/** The kinds of drag items this drop target accepts */

24

accept: TargetType;

25

/** Drop target options */

26

options?: DropTargetOptions;

27

/** Called when a compatible item is dropped */

28

drop?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => DropResult | undefined;

29

/** Called when an item is hovered over the component */

30

hover?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => void;

31

/** Determines whether the drop target can accept the item */

32

canDrop?: (item: DragObject, monitor: DropTargetMonitor<DragObject, DropResult>) => boolean;

33

/** Function to collect properties from monitor */

34

collect?: (monitor: DropTargetMonitor<DragObject, DropResult>) => CollectedProps;

35

}

36

37

type FactoryOrInstance<T> = T | (() => T);

38

```

39

40

**Basic Usage:**

41

42

```typescript

43

import React, { useState } from "react";

44

import { useDrop } from "react-dnd";

45

46

interface DropItem {

47

id: string;

48

name: string;

49

}

50

51

function DropZone() {

52

const [droppedItems, setDroppedItems] = useState<DropItem[]>([]);

53

54

const [{ isOver, canDrop }, drop] = useDrop({

55

accept: "card",

56

drop: (item: DropItem) => {

57

setDroppedItems(prev => [...prev, item]);

58

return { dropped: true };

59

},

60

collect: (monitor) => ({

61

isOver: monitor.isOver(),

62

canDrop: monitor.canDrop(),

63

}),

64

});

65

66

return (

67

<div

68

ref={drop}

69

style={{

70

backgroundColor: isOver && canDrop ? "lightgreen" : "lightgray",

71

minHeight: 200,

72

padding: 16

73

}}

74

>

75

{canDrop ? "Drop items here!" : "Drag compatible items here"}

76

{droppedItems.map(item => (

77

<div key={item.id}>{item.name}</div>

78

))}

79

</div>

80

);

81

}

82

```

83

84

**Advanced Usage with All Options:**

85

86

```typescript

87

import React, { useState, useRef } from "react";

88

import { useDrop } from "react-dnd";

89

90

function AdvancedDropTarget({ onItemMoved, acceptedTypes }) {

91

const [dropHistory, setDropHistory] = useState([]);

92

const dropRef = useRef(null);

93

94

const [collected, drop] = useDrop({

95

// Accept multiple types

96

accept: acceptedTypes,

97

98

// Conditional drop acceptance

99

canDrop: (item, monitor) => {

100

return item.status !== "locked" && item.id !== "restricted";

101

},

102

103

// Hover handler for drag feedback

104

hover: (item, monitor) => {

105

if (!dropRef.current) return;

106

107

const dragIndex = item.index;

108

const hoverIndex = findHoverIndex(monitor.getClientOffset());

109

110

if (dragIndex !== hoverIndex) {

111

// Provide visual feedback during hover

112

highlightDropPosition(hoverIndex);

113

}

114

},

115

116

// Drop handler with detailed result

117

drop: (item, monitor) => {

118

const dropOffset = monitor.getClientOffset();

119

const dropResult = {

120

item,

121

position: calculateDropPosition(dropOffset),

122

timestamp: Date.now(),

123

isExactDrop: monitor.isOver({ shallow: true })

124

};

125

126

setDropHistory(prev => [...prev, dropResult]);

127

onItemMoved?.(item, dropResult.position);

128

129

return dropResult;

130

},

131

132

// Comprehensive collect function

133

collect: (monitor) => ({

134

isOver: monitor.isOver(),

135

isOverShallow: monitor.isOver({ shallow: true }),

136

canDrop: monitor.canDrop(),

137

itemType: monitor.getItemType(),

138

draggedItem: monitor.getItem(),

139

dropResult: monitor.getDropResult(),

140

didDrop: monitor.didDrop(),

141

}),

142

});

143

144

return (

145

<div

146

ref={(node) => {

147

drop(node);

148

dropRef.current = node;

149

}}

150

style={{

151

backgroundColor: collected.isOver && collected.canDrop ? "lightblue" : "white",

152

border: collected.canDrop ? "2px dashed blue" : "1px solid gray",

153

minHeight: 300,

154

position: "relative"

155

}}

156

>

157

<div>Drop Target - {collected.itemType || "No item"}</div>

158

{collected.isOver && !collected.canDrop && (

159

<div style={{ color: "red" }}>Cannot drop this item here</div>

160

)}

161

{dropHistory.length > 0 && (

162

<div>

163

<h4>Drop History:</h4>

164

{dropHistory.map((drop, index) => (

165

<div key={index}>{drop.item.name} at {drop.timestamp}</div>

166

))}

167

</div>

168

)}

169

</div>

170

);

171

}

172

```

173

174

### Drop Target Connector

175

176

The useDrop hook returns a connector function for attaching drop functionality to DOM elements.

177

178

```typescript { .api }

179

/** Function to connect DOM elements as drop targets */

180

type ConnectDropTarget = DragElementWrapper<any>;

181

182

type DragElementWrapper<Options> = (

183

elementOrNode: ConnectableElement,

184

options?: Options

185

) => React.ReactElement | null;

186

187

type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;

188

```

189

190

**Usage Examples:**

191

192

```typescript

193

function CustomConnectorExample() {

194

const [{ isOver }, drop] = useDrop({

195

accept: "item",

196

collect: (monitor) => ({ isOver: monitor.isOver() })

197

});

198

199

return (

200

<div>

201

{/* Basic drop connector */}

202

<div ref={drop}>Basic drop area</div>

203

204

{/* Connector with JSX element */}

205

{drop(

206

<div style={{

207

border: isOver ? "2px solid blue" : "1px dashed gray",

208

padding: 20

209

}}>

210

Custom drop area

211

</div>

212

)}

213

</div>

214

);

215

}

216

```

217

218

### Drop Target Monitor

219

220

Monitor interface providing information about the current drop operation.

221

222

```typescript { .api }

223

interface DropTargetMonitor<DragObject = unknown, DropResult = unknown> {

224

/** Returns true if drop is allowed */

225

canDrop(): boolean;

226

/** Returns true if pointer is over this target */

227

isOver(options?: { shallow?: boolean }): boolean;

228

/** Returns the type of item being dragged */

229

getItemType(): Identifier | null;

230

/** Returns the dragged item data */

231

getItem<T = DragObject>(): T;

232

/** Returns drop result after drop completes */

233

getDropResult<T = DropResult>(): T | null;

234

/** Returns true if drop was handled */

235

didDrop(): boolean;

236

/** Returns initial pointer coordinates when drag started */

237

getInitialClientOffset(): XYCoord | null;

238

/** Returns initial drag source coordinates */

239

getInitialSourceClientOffset(): XYCoord | null;

240

/** Returns current pointer coordinates */

241

getClientOffset(): XYCoord | null;

242

/** Returns pointer movement since drag start */

243

getDifferenceFromInitialOffset(): XYCoord | null;

244

/** Returns projected source coordinates */

245

getSourceClientOffset(): XYCoord | null;

246

}

247

```

248

249

## Configuration Options

250

251

### Drop Target Options

252

253

```typescript { .api }

254

/** Flexible options object for drop target configuration */

255

type DropTargetOptions = any;

256

```

257

258

## Common Patterns

259

260

### Multiple Item Types

261

262

```typescript

263

function MultiTypeDropTarget() {

264

const [{ isOver, itemType }, drop] = useDrop({

265

accept: ["card", "item", "file"],

266

drop: (item, monitor) => {

267

const type = monitor.getItemType();

268

269

switch (type) {

270

case "card":

271

handleCardDrop(item);

272

break;

273

case "item":

274

handleItemDrop(item);

275

break;

276

case "file":

277

handleFileDrop(item);

278

break;

279

}

280

281

return { acceptedType: type };

282

},

283

collect: (monitor) => ({

284

isOver: monitor.isOver(),

285

itemType: monitor.getItemType(),

286

}),

287

});

288

289

return (

290

<div ref={drop}>

291

{isOver && <div>Dropping {itemType}...</div>}

292

Multi-type drop zone

293

</div>

294

);

295

}

296

```

297

298

### Nested Drop Targets

299

300

```typescript

301

function NestedDropTargets() {

302

const [{ isOverOuter }, dropOuter] = useDrop({

303

accept: "item",

304

drop: (item, monitor) => {

305

// Only handle if not dropped on inner target

306

if (!monitor.didDrop()) {

307

return { droppedOn: "outer" };

308

}

309

},

310

collect: (monitor) => ({

311

isOverOuter: monitor.isOver({ shallow: true }),

312

}),

313

});

314

315

const [{ isOverInner }, dropInner] = useDrop({

316

accept: "item",

317

drop: (item) => {

318

return { droppedOn: "inner" };

319

},

320

collect: (monitor) => ({

321

isOverInner: monitor.isOver(),

322

}),

323

});

324

325

return (

326

<div ref={dropOuter} style={{ padding: 20, border: "1px solid blue" }}>

327

Outer Target {isOverOuter && "(hovering outer)"}

328

<div ref={dropInner} style={{ padding: 20, border: "1px solid red" }}>

329

Inner Target {isOverInner && "(hovering inner)"}

330

</div>

331

</div>

332

);

333

}

334

```

335

336

### Conditional Dropping

337

338

```typescript

339

function ConditionalDropTarget({ isEnabled, maxItems, currentItems }) {

340

const [{ isOver, canDrop }, drop] = useDrop({

341

accept: "item",

342

canDrop: (item) => {

343

return isEnabled &&

344

currentItems.length < maxItems &&

345

!currentItems.find(existing => existing.id === item.id);

346

},

347

drop: (item) => {

348

return { accepted: true, timestamp: Date.now() };

349

},

350

collect: (monitor) => ({

351

isOver: monitor.isOver(),

352

canDrop: monitor.canDrop(),

353

}),

354

});

355

356

return (

357

<div

358

ref={drop}

359

style={{

360

backgroundColor: isOver && canDrop ? "lightgreen" :

361

isOver && !canDrop ? "lightcoral" : "white",

362

}}

363

>

364

{!isEnabled && "Drop target disabled"}

365

{isEnabled && currentItems.length >= maxItems && "Maximum items reached"}

366

{isEnabled && currentItems.length < maxItems && "Ready to accept items"}

367

</div>

368

);

369

}

370

```

371

372

### Position-aware Dropping

373

374

```typescript

375

function PositionAwareDropTarget() {

376

const [items, setItems] = useState([]);

377

378

const [{ isOver }, drop] = useDrop({

379

accept: "item",

380

hover: (item, monitor) => {

381

if (!ref.current) return;

382

383

const clientOffset = monitor.getClientOffset();

384

const targetBounds = ref.current.getBoundingClientRect();

385

386

// Calculate relative position within drop target

387

const relativeX = (clientOffset.x - targetBounds.left) / targetBounds.width;

388

const relativeY = (clientOffset.y - targetBounds.top) / targetBounds.height;

389

390

// Provide visual feedback based on position

391

updateDropIndicator(relativeX, relativeY);

392

},

393

drop: (item, monitor) => {

394

const clientOffset = monitor.getClientOffset();

395

const targetBounds = ref.current.getBoundingClientRect();

396

397

const position = {

398

x: clientOffset.x - targetBounds.left,

399

y: clientOffset.y - targetBounds.top,

400

};

401

402

setItems(prev => [...prev, { ...item, position }]);

403

return { position };

404

},

405

collect: (monitor) => ({

406

isOver: monitor.isOver(),

407

}),

408

});

409

410

const ref = useRef(null);

411

412

return (

413

<div

414

ref={(node) => {

415

drop(node);

416

ref.current = node;

417

}}

418

style={{ position: "relative", width: 400, height: 300, border: "1px solid black" }}

419

>

420

{items.map((item, index) => (

421

<div

422

key={index}

423

style={{

424

position: "absolute",

425

left: item.position.x,

426

top: item.position.y,

427

padding: 4,

428

backgroundColor: "lightblue",

429

}}

430

>

431

{item.name}

432

</div>

433

))}

434

</div>

435

);

436

}

437

```