Skip to content

Commit

Permalink
Improve Hooks Documentation (#3043)
Browse files Browse the repository at this point in the history
* refactor: break out react-dnd's internal package structure

* chore: cut major semver due to re-layout

* fix: remove extraneous deps

* fix: jest tests

* chore: cut semver

* fix: tsconfig updates

* refactor: roll pkgs back into react-dnd using a flattened folder structure

* refactor: update DragDropManager construction; fix tests/linting

* chore: update semver document

* ci: remove redundant build script

* build: nvm

* fix: top-level build steps

* docs: brush up some api doc pages

* docs: move monitoring state towards bottom of article list

* chore: cut semver

* docs: update alex config
  • Loading branch information
darthtrevino committed Feb 19, 2021
1 parent 0f6e8c5 commit a850aa0
Show file tree
Hide file tree
Showing 22 changed files with 387 additions and 157 deletions.
3 changes: 3 additions & 0 deletions .alexrc
@@ -0,0 +1,3 @@
{
"allow": ["hook", "hooks"]
}
2 changes: 2 additions & 0 deletions .yarn/versions/721b19b0.yml
@@ -0,0 +1,2 @@
declined:
- react-dnd-documentation
140 changes: 22 additions & 118 deletions packages/docsite/markdown/docs/00 Quick Start/Overview.md
Expand Up @@ -10,16 +10,6 @@ React DnD is unlike most of the drag and drop libraries out there, and it can be
Some of these concepts resemble the [Flux](http://facebook.github.io/flux/) and [Redux](https://github.com/reactjs/react-redux) architectures.
This is not a coincidence, as React DnD uses Redux internally.

### Backends

React DnD is built on top of the [HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_and_drop). It is a reasonable default because it screenshots the dragged DOM node and uses it as a “drag preview” out of the box. It's handy that you don't have to do any drawing as the cursor moves. This API is also the only way to handle the file drop events.

Unfortunately, the HTML5 drag and drop API also has some downsides. It does not work on touch screens, and it provides less customization opportunities on IE than in other browsers.

This is why **the HTML5 drag and drop support is implemented in a pluggable way** in React DnD. You don't have to use it. You can write a different implementation, based on touch events, mouse events, or something else entirely. Such pluggable implementations are called the _backends_ in React DnD. Only the [HTML5 backend](/docs/backends/html5) comes with the library, but more may be added in the future.

The backends perform a similar role to that of React's synthetic event system: **they abstract away the browser differences and process the native DOM events.** Despite the similarities, React DnD backends do not have a dependency on React or its synthetic event system. Under the hood, all the backends do is translate the DOM events into the internal Redux actions that React DnD can process.

### Items and Types

Like Flux (or Redux), React DnD uses data, and not the views, as the source of truth. When you drag something across the screen, we don't say that a component, or a DOM node is being dragged. Instead, we say that an _item_ of a certain _type_ is being dragged.
Expand Down Expand Up @@ -97,129 +87,43 @@ Whenever you want to make a component or some part of it draggable, you need to

The _drop targets_ are very similar to the drag sources. The only difference is that a single drop target may register for several item types at once, and instead of producing an item, it may handle its hover or drop.

### Higher-Order Components and Decorators

How do you wrap your components? What does wrapping even mean? If you have not worked with higher-order components before, go ahead and read [this article](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750), as it explains the concept in detail.

**A higher-order component is a function that takes a React component class, and returns another React component class.** The wrapping component provided by the library renders _your_ component in its `render` method and forwards the props to it, but also adds some useful behavior.

In React DnD, [`DragSource`](/docs/api/drag-source) and [`DropTarget`](/docs/api/drop-target), as well as a few other top-level exported functions, are in fact higher-order components. They breathe the drag and drop magic into your components.

One caveat of using them is that they require _two_ function applications. For example, here's how to wrap `YourComponent` in a [`DragSource`](/docs/api/drag-source):

```jsx
import { DragSource } from 'react-dnd'

class YourComponent {
/* ... */
}

export default DragSource(/* ... */)(YourComponent)
```
### Backends

Notice how, after specifying the [`DragSource`](/docs/api/drag-source) parameters in the first function call, there is a _second_ function call, where you finally pass your class. This is called [currying](http://en.wikipedia.org/wiki/Currying), or [partial application](http://en.wikipedia.org/wiki/Partial_application), and is necessary for the [decorator syntax](http://github.com/wycats/javascript-decorators) to work out of the box:
React DnD uses the [HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_and_drop). It is a reasonable default because it screenshots the dragged DOM node and uses it as a “drag preview” out of the box. It's handy that you don't have to do any drawing as the cursor moves. This API is also the only way to handle the file drop events.

```jsx
import { DragSource } from 'react-dnd'
Unfortunately, the HTML5 drag and drop API also has some downsides. It does not work on touch screens, and it provides less customization opportunities on IE than in other browsers.

@DragSource(/* ... */)
export default class YourComponent {
/* ... */
}
```
This is why **the HTML5 drag and drop support is implemented in a pluggable way** in React DnD. You don't have to use it. You can write a different implementation, based on touch events, mouse events, or something else entirely. Such pluggable implementations are called the _backends_ in React DnD.

You don't have to use this syntax, but if you like it, you can enable it by transpiling your code with [Babel](http://babeljs.io), and putting `{ "stage": 1 }` into your [.babelrc file](https://babeljs.io/docs/usage/babelrc/).
The library currently ships with the [HTML backend](docs/backends/html5), which should be sufficient for most web applications. There is also a [Touch backend](/docs/backend/touch) that can be used for mobile web applications.

Even if you don't plan to use decorators, the partial application can still be handy, because you can combine several [`DragSource`](/docs/api/drag-source) and [`DropTarget`](/docs/api/drop-target) declarations in JavaScript using a functional composition helper such as [`_.flow`](https://lodash.com/docs#flow). With decorators, you can stack the decorators to achieve the same effect.
The backends perform a similar role to that of React's synthetic event system: **they abstract away the browser differences and process the native DOM events.** Despite the similarities, React DnD backends do not have a dependency on React or its synthetic event system. Under the hood, all the backends do is translate the DOM events into the internal Redux actions that React DnD can process.

```jsx
import { DragSource, DropTarget } from 'react-dnd'
import flow from 'lodash/flow'

class YourComponent {
render() {
const { connectDragSource, connectDropTarget } = this.props
return connectDragSource(
connectDropTarget()
/* ... */
)
}
}
### Hooks vs Higher-Order Components

export default flow(DragSource(/* ... */), DropTarget(/* ... */))(YourComponent)
```
Now you should have an understanding of the various moving pieces of React DnD:

### Putting It All Together
- Item objects and types
- DnD state via flux
- Monitors for observing DnD state
- Collector functions for turning monitor output into consumable props
- Connectors for attaching the DnD state machine to view nodes (e.g. DOM elements)

Below is an example of wrapping an existing `Card` component into a drag source.
Now let's talk about how these pieces come together in your components. You have two options: a modern hooks-based API and the classic Decorators-based API.

```jsx
import React from 'react'
import { DragSource } from 'react-dnd'

// Drag sources and drop targets only interact
// if they have the same string type.
// You want to keep types in a separate file with
// the rest of your app's constants.
const Types = {
CARD: 'card'
}
### Hooks

/**
* Specifies the drag source contract.
* Only `beginDrag` function is required.
*/
const cardSource = {
beginDrag(props) {
// Return the data describing the dragged item
const item = { id: props.id }
return item
},

endDrag(props, monitor, component) {
if (!monitor.didDrop()) {
return
}

// When dropped on a compatible target, do something
const item = monitor.getItem()
const dropResult = monitor.getDropResult()
CardActions.moveCardToList(item.id, dropResult.listId)
}
}
Modern React applications have replaced the Higher-Order-Component pattern with hooks. Hooks are a feature of React, introduced in 16.8, that allow for developers to write stateful function components. They also fantastic for managing stateful components, and also for interacting with external stateful systems (\***cough**\* like a Drag-and-Drop engine \***cough**\*).

/**
* Specifies which props to inject into your component.
*/
function collect(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDragSource: connect.dragSource(),
// You can ask the monitor about the current drag state:
isDragging: monitor.isDragging()
}
}
If you are unfamiliar with React hooks, refer to the React blog post, [Introducing Hooks](https://reactjs.org/docs/hooks-intro.html).

function Card(props) {
// Your component receives its own props as usual
const { id } = props
React-DnD provides hooks that connect your components to the DnD engine, and allow you to collect monitor state for rendering.

// These two props are injected by React DnD,
// as defined by your `collect` function above:
const { isDragging, connectDragSource } = props
For an overview of the hooks-based API, refer to the [Hooks Overview](/docs/api/hooks-overview) page.

return connectDragSource(
<div>
I am a draggable card number {id}
{isDragging && ' (and I am being dragged now)'}
</div>
)
}
### Higher-Order Components and Decorators

// Export the wrapped version
export default DragSource(Types.CARD, cardSource, collect)(Card)
```
### Conclusion

Now you know enough about React DnD to explore the rest of the documentation!
The [tutorial](/docs/tutorial) is a great place to start.
The [hooks overview](/docs/api/hooks-overview) and [decorators overview](/docs/api/decorators-overview) documentation pages are great places to start. Or jump straight into the [tutorial app](/docs/tutorial) and build a chess game!
Expand Up @@ -7,7 +7,7 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# DragLayerMonitor

`DragLayerMonitor` is an object passed to a collecting function of the [`DragLayer`](/docs/api/drag-layer). Its methods let you get information about the global drag state.
`DragLayerMonitor` is an object passed to a collecting function of a [hooks-based](/docs/api/use-drag-layer) or [decorator-based](/docs/api/drag-layer) drag layer. Its methods let you get information about the global drag state.

### Methods

Expand Down
Expand Up @@ -7,7 +7,7 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# DragSourceMonitor

`DragSourceMonitor` is an object passed to a collecting function of the [`DragSource`](/docs/api/drag-source). Its methods let you get information about the drag state of a specific drag source. The specific drag source bound to that monitor is called the monitor's _owner_ below.
`DragSourceMonitor` is an object passed to a collecting function of a [hooks-based](/docs/api/use-drag) or [decorator-based](/docs/api/drag-source) dragging source. Its methods let you get information about the drag state of a specific drag source. The specific drag source bound to that monitor is called the monitor's _owner_ below.

### Methods

Expand Down
Expand Up @@ -7,7 +7,7 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# DropTargetMonitor

`DropTargetMonitor` is an object passed to a collecting function of the [`DropTarget`](/docs/api/drop-target). Its methods let you get information about the drag state of a specific drop target. The specific drop target bound to that monitor is called the monitor's _owner_ below.
`DropTargetMonitor` is an object passed to a collecting function of a [hooks-based](/docs/api/use-drop) or [decorator-based](/docs/api/drop-target). Its methods let you get information about the drag state of a specific drop target. The specific drop target bound to that monitor is called the monitor's _owner_ below.

### Methods

Expand Down
34 changes: 34 additions & 0 deletions packages/docsite/markdown/docs/03 Using Hooks/DndProvider.md
@@ -0,0 +1,34 @@
---
path: '/docs/api/dnd-provider'
title: 'DndProvider'
---

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# DndProvider

The DndProvider component provides React-DnD capabilities to your application. This must be
injected with a backend via the `backend` prop, but it may be injected with a `window` object.

### Usage

```jsx
import { HTML5Backend } from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'

export default class YourApp {
render() {
return (
<DndProvider backend={HTML5Backend}>
/* Your Drag-and-Drop Application */
</DndProvider>
)
}
}
```

### Props

- **`backend`**: Required. A React DnD backend. Unless you're writing a custom one, you probably want to use the [HTML5 backend](/docs/backends/html5) that ships with React DnD.
- **`context`**: Optional. The backend context used to configure the backend. This is dependent on the backend implementation.
- **`options`**: Optional. An options object used to configure the backend. This is dependent on the backend implementation.
36 changes: 36 additions & 0 deletions packages/docsite/markdown/docs/03 Using Hooks/DragPreviewImage.md
@@ -0,0 +1,36 @@
---
path: '/docs/api/drag-preview-image'
title: 'DragPreviewImage'
---

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# DragPreviewImage

A Component to render an HTML Image element as a disconnected drag preview.

### Usage

```jsx
import { DragSource, DragPreviewImage } from 'react-dnd'

function DraggableHouse({ connectDragSource, connectDragPreview }) {
return (
<>
<DragPreviewImage src="house_dragged.png" connect={connectDragPreview} />
<div ref={connectDragSource}>🏠</div>
</>
)
}
export default DragSource(
/* ... */
(connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview()
})
)
```

### Props

- **`connect`**: Required. The drag preview connector function
75 changes: 75 additions & 0 deletions packages/docsite/markdown/docs/03 Using Hooks/HooksOverview.md
@@ -0,0 +1,75 @@
---
path: '/docs/api/hooks-overview'
title: 'Overview'
---

_New to React DnD? [Read the overview](/docs/overview) before jumping into the docs._

# Using the Hooks API

The hooks-based React DnD API leverages one of the significant newer features of React. Hooks have dramatically impacted how most people write their Reoct components, and are the recommended approach for writing stateful and effectful code within React. Prior to hooks, the React community poured a lot of effort into Higher Order Components and Decorator-based libraries. After hooks were introduced to the React community, there has been a dramatic shift in switching towards approaches and libraries that utilize hooks over decorator-based techniques.

In the overview page, it is pointed out that Drag-and-drop interations are inherently _stateful_. Because of this React DnD has been designed to take advantage of the Flux data pattern and model drag-and-drop state using actions and reducers (independent of React). Hooks are the perfect way to utilize a stateful data source in React. In fact, this is the approach taken by many state-management libraries in React!

There are three primary hooks that are provided to wire your components into React DnD, and a fourth is provided to give you a seam into React DnD (for testing or development purposes)

- [`useDrag`](/docs/api/use-drag)
- [`useDrop`](/docs/api/use-drop)
- [`useDragLayer`](/docs/api/use-drag-layer)
- [`useDragDropManager`](/docs/api/use-drag-drop-manager) (_dev/test hook_)

## Basic Example

To start using hooks, let's make a box draggable.

```jsx
import { useDrag } from 'react-dnd'

function Box() {
const [{ isDragging }, drag, dragPreview] = useDrag({
// "type" is required. It is used by the "accept" specification of drop targets.
item: { type: 'BOX' },
// The collect function utilizes a "monitor" instance (see the Overview for what this is)
// to pull important pieces of state from the DnD system.
collect: (monitor) => ({
isDragging: monitor.isDragging()
})

return (
{/* This is optional. The dragPreview will be attached to the dragSource by default */}
<div ref={dragPreview} style={{ opacity: isDragging ? 0.5 : 1}}>
{/* The drag ref marks this node as being the "pick-up" node */}
<div role="Handle" ref={drag}>
</div>
)
})
}
```

Now, let's make something for this to drag into.

```jsx
function Bucket() {
const [{ canDrop, isOver }, drop] = useDrop({
// The type (or types) to accept - strings or symbols
accept: 'BOX',
// Props to collect
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
})

return (
<div
ref={drop}
role={'Dustbin'}
style={{ backgroundColor: isActive ? 'red' : 'white' }}
>
{isActive ? 'Release to drop' : 'Drag a box here'}
</div>
)
}
```

To explore further, read the individual hook API documentation, or check out the [hook-based examples](https://github.com/react-dnd/react-dnd/tree/main/packages/examples-hooks) on GitHub.
Expand Up @@ -9,16 +9,21 @@ _New to React DnD? [Read the overview](/docs/overview) before jumping into the d

# useDrag

A hook to use the current component as a drag-source.
The `useDrag`hook provides a way to wire your component into the DnD system as a _drag source_. By passing in a specification object into `useDrag`, you declaratively describe the data `item` that will be passed to the DnD system, describe what props to `collect`, and more. The `useDrag` hooks returns a few key items: a set of collected props, and refs that may be attached to _drag source_ and _drag preview_ elements

```jsx
import { useDrag } from 'react-dnd'

function DraggableComponent(props) {
const [collectedProps, drag] = useDrag({
const [collected, drag, dragPreview] = useDrag({
item: { id, type }
})
return <div ref={drag}>...</div>
return collected.isDragging ?
<div ref={dragPreview> : (
<div ref={drag} {...collected}>
...
</div>
)
}
```
Expand All @@ -28,9 +33,9 @@ function DraggableComponent(props) {
#### Return Value Array
- **`Index 0`**: An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
- **`Index 1`**: A connector function for the drag source. This must be attached to the draggable portion of the DOM.
- **`Index 2`**: A connector function for the drag preview. This may be attached to the preview portion of the DOM.
- **`[0] - Collected Props`**: An object containing collected properties from the collect function. If no `collect` function is defined, an empty object is returned.
- **`[1] - DragSource Ref`**: A connector function for the drag source. This must be attached to the draggable portion of the DOM.
- **`[2] - DragPreview Ref`**: A connector function for the drag preview. This may be attached to the preview portion of the DOM.
### Specification Object Members
Expand Down

0 comments on commit a850aa0

Please sign in to comment.