React hooks have recently been announced.
Would we benefit from moving from a render props api to a hook based api for react-beautiful-dnd? If so, what would it even look like?
I have not thought very much about this, but I wanted to open it for discussion
Oh wow this is an interesting question. I think a hook based api would have to come along with a render prop based api. Otherwise, you wouldn't be able to consume the hooks in non-function components.
I'm not 100% sure what a useDraggable or useDroppable would even look like.
I would assume that it would use useEffect and useContext to do its work?
Maybe something like this?
function Card({ itemId, index }) {
const draggable = useDraggable({ draggableId: itemId, index });
return (
<div {...draggable.draggableProps} ref={draggable.innerRef}>
<h1 {...draggable.dragHandleProps}>Drag me by the title</h1>
<p>...</p>
</div>
);
}
I mean, this is just syntactically how I imagine it. Not sure how to make it workable, not familiar with the inner workings of this lib (would love to but haven't had time).
Yup, and if we were sticking with the way that useState works it would probably actually return
const [provided, snapshot] = useDraggable({ id, index, })
that way you could easily rename, and it is similar to the way react core works
I wouldn't extrapolate that feature of useState to this case. I'm pretty sure useState does it like that to make it easier to rename the two things to your liking, since is common to use more than one state in several situations.
With something like this hypothetical useDraggable, that's not the case as much. Also, keep in mind there will be more stuff in that returned value, such as isDragging. What if you do not need everything? You'd need to remember the order of the stuff to know how to destructure it. With useState that's not an issue because is just two things and you pretty much always need them both.
@gnapse definitely, however the way I picture it, useDraggable would only return two things. For example, check this from the docs.
import { Draggable } from 'react-beautiful-dnd';
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<h4>My draggable</h4>
</div>
)}
</Draggable>;
Here we get a provided object as well as a snapshot object. Whether it returns an array or an object doesn't matter so much to me, just that we have access to both.
Unless you are suggesting all the properties get merged on one draggable return object, which is an interesting thought, but different from the current api i'm used to
Oh well, yes. Maybe. Makes more sense now.
This seems like a reasonable API
function Card({ itemId, index }) {
const [provided, snapshot] = useDraggable({ draggableId: itemId, index });
return (
<div {...provided.draggableProps} {...provided.dragHandleProps } ref={provided.innerRef}>
Drag me!
</div>
);
}
and
function List({ listId }) {
const [provided, snapshot] = useDroppable({ draggableId: itemId, index });
return (
<div {...provided.droppableProps} ref={provided.innerRef}>
<p>...</p>
{provided.placeholder}
</div>
);
}
It seems to be the hook equivalent of the render props api we are providing now
@alexreardon
I like this more:
const {provided, snapshot} = useDraggable({ draggableId: itemId, index });
It is not even a single character longer and serves probably >90% of use cases.
I'd argue that the minor advantage of easily renaming the objects returned is not worth the disadvantages returning an array:
The likelihood of even wanting to rename it is small so I think staying with object spreading and keeping the current names as a convention is better.
If you really feel that you need to rename it you could still do:
const {provided: renamedProvided, snapshot: renamedSnapshot} = useDraggable({ draggableId: itemId, index });
Not too hard if you ask me.
useState is a different story because:
useState has no semantic meaning in your app but you usually want to give it a more meaningful name other than state and setState.In short that means that in the majority of the cases you actually want to rename the values returned by useState. This is the primary reason they decided not to return an object.
I don't see that being true for higher level hooks like useDraggable which are already very explicit about their use case and what they return. And they are also not used multiple times in the same component very often.
I feel the same way. I fear that because of useState using array destructuring (which is justified in its case for the reasons stated above) now many new hooks-enabled libraries will follow along without strong reasons.
You hardly ever use more than one draggable in the same context, and the same goes for droppable. And even if you did, you'd be better off not destructuring it, give the return value two meaningful names, and use those names' properties in the context they need to be used.
While this does look awesome from the outside, I'm curious how we are going to achieve this effect.
Draggable does a lot more then just feed in those items to it's children, it also wraps those children in the handle as well as the dimension publisher.
I would argue that this is a case where render props/children as a function makes more sense then using hooks, since there is some hierarchy and extra rendering happening.
Any ideas how we could even begin to achieve this effect with hooks?
You hardly ever use more than one draggable in the same context, and the same goes for droppable. And even if you did, you'd be better off not destructuring it, give the return value two meaningful names, and use those names' properties in the context they need to be used.
I actually do agree with the object notation over the array notation in this usecase, gives more extendability down the line.
One thing that is a bit more common then double draggable or double droppable is a draggable inside of a droppable in the same render. But the Droppable and Draggable components expose a provided and a snapshot property.
However, these cases don't feel common enough to warrant array notation
@YurkaninRyan
Draggable does a lot more then just feed in those items to it's children, it also wraps those children in the handle as well as the dimension publisher.
Any ideas how we could even begin to achieve this effect with hooks?
That is an interesting question. I guess it should be possible to rewrite the DragHandle and DimensionPublisher to be custom hooks themselfs which will then be used by the useDraggable hook internally. Therefore the whole current implementation as class based components has to be rewritten as hooks.
As far as I understood you should be able to transform any class based component logic into a hook including state and lifecycle methods and effectively make the components obsolete. The only problem would be if a component renders a context provider. You can't replace that with a hook.
For DimensionPublisher it would roughly look like that:
const useDraggableDimensionPublisher = ({draggableId, droppableId, type, index, ref}) => {
// implementation of dimension publisher
// probably uses `useEffect()` to implement lifecycle side effects
useEffect(/* lifecycle implementation here */)
}
const useDraggable = ({draggableId, index}) => {
const {droppableId, type} = useContext(/* ... */);
const draggableRef = useRef();
// calls dimension publisher hook to use side effects
useDraggableDimensionPublisher({draggableId, droppableId, type, index, ref: draggableRef.current})
// implementation of draggable ...
return {provided, snapshot};
}
My current thinking:
useDraggable and useDroppableDraggable and Droppable component with the same function as child api as we currently have. This would allow people to continue to use render props if they want to// Assuming a useDraggable(props) hook extends
function Draggable({draggableId, type, children}) {
const [provided, snapshot] = useDraggable({draggableId, type});
return children(provided, snapshot);
}
I'd love to get working on some of these hooks, what hooks would make the most sense to start with?
Some bad news: there is no useProvider react hook. So there is no way for a hook to setup context.
This is bad because Droppable sets up some context information to be used by Draggable. (type and droppableId).
While we can still move internally to hooks, this might be a dealbreaker to exposing a hook based public api
Could that context information instead be shifted up to DragDropContext and contained in some sort of internal map by droppable id?
Interesting @YurkaninRyan
Important milestone: everything has been converted to hooks internally, and it mounts without exploding

It then turns into a hot mess when you start to drag
I have a branch that is working and passing all tests. I am doing some finial performance testing #1208. You can play with it here: https://deploy-preview-1208--react-beautiful-dnd.netlify.com/?path=/story/single-vertical-list--basic
The branch is currently using react-redux@7-beta and I think we should wait until it is out of beta before leaning on react-redux@7
useMemoOnereact-redux@7 /cc @markeriksonUntil we a hook can setup context, we will not be moving to a hook based public API. We might investigate workarounds, but for now the render prop API will remain
Pardon me for intruding, but I don't understand the need for the missing useProvider hook to setup context here.
[...]there is no useProvider react hook. So there is no way for a hook to setup context.
This is bad because
Droppablesets up some context information to be used byDraggable. (type and droppableId).[...]
Until we a hook can setup context, we will not be moving to a hook based public API. [...]
Droppable is already rendering a DroppableDimensionPublisher which is aware of type and droppableId. Why not have it also render a context provider (e.g. <DroppableContext.Provider>)? That way any child (including Draggable) can useContext(DroppableContext).
Am I missing something here?
We cannot have a Droppable be a hook right now as it sets up context for a Draggable to consumer. There is no way for a hook to set up context. Droppable has moved over to being a function component that uses hooks, and renders a Context.Provider for a Draggable to consume
Ah, I didn't realize you were trying to make Droppable _purely_ a hook (avoid the Component altogether). I thought the objective was simply to remove the render props syntax.
Thanks for the clarification!
tl;dr
As for setting up context in a hook, perhaps a hook can instead return a component which sets up context? I started thinking along those lines and came up with the following, for what it's worth.
Original syntax
import { useState } from 'react'
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'
export default function App () {
const [items, setItems] = useState(['one', 'two', 'three'])
function onDragEnd ({ type, reason, destination, source }) {
if (type === 'DEFAULT' && reason === 'DROP' && destination) {
const newItems = Array.from(items)
const moved = newItems.splice(source.index, 1)
newItems.splice(destination.index, 0, moved)
setItems(newItems)
}
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId='dropzone'>
{(droppableProvided, droppableSnapshot) => {
return (
<ul ref={droppableProvided.innerRef}>
{items.map((item, idx) => {
return (
<Draggable draggableId={`draggable-${item}`} index={idx}>
{(draggableProvided, draggableSnapshot) => {
return (
<li
key={item}
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>{item}</li>
)
}}
</Draggable>
)
})}
{droppableProvided.placeholder}
</ul>
)
}}
</Droppable>
</DragDropContext>
)
}
Potential new syntax
import { useState } from 'react'
import { useDragDrop } from 'react-beautiful-dnd'
export default function App () {
const [items, setItems] = useState(['one', 'two', 'three'])
const [DragDropContext, makeDroppable, makeDraggable] = useDragDrop()
function onDragEnd ({ type, reason, destination, source }) {
if (type === 'DEFAULT' && reason === 'DROP' && destination) {
const newItems = Array.from(items)
const moved = newItems.splice(source.index, 1)
newItems.splice(destination.index, 0, moved)
setItems(newItems)
}
}
const [Droppable, droppableProvided, droppableSnapshot] = makeDroppable({
id: 'dropzone'
})
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable>
<ul ref={droppableProvided.innerRef}>
{items.map((item, idx) => {
const [Draggable, draggableProvided, draggableSnapshot] = makeDraggable({
id: `draggable-${item}`,
index: idx
})
return (
<Draggable>
<li
key={item}
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>{item}</li>
</Draggable>
)
})}
{droppableProvided.placeholder}
</ul>
</Droppable>
</DragDropContext>
)
}
Again, I'm not sure how good of an idea this is, I'm just trying to help generate ideas...
An interesting idea @shawninder. I did consider it, but I was not sure there was much value in this pattern over just keeping with the existing render props api.
The internal hook refactor has shipped in 11.0
For now, until a hook can setup context we won't be able to move to a public hook api. Based on that I will close this issue. I gave it a really good shot!
It seems that useProvider is not going to happen. It is suggested and rejected in facebook/react#14534. And this blog post mentions useProvider as something that doesn鈥檛 work well as a hook.
It might make sense for hooks not to be able to set up context as an architectural decision. The consequence of the decision is that we cannot move to a purely hooks based api. It is not the end of the world 馃槉
Why does the API need to be a purely hooks API though? Why not expose just useDraggable and leave Droppable as is?
I do agree it鈥檚 not a big deal though, even with hooks being a thing, the render prop API still makes sense when the abstraction is a visual one and not a logical one, which is the case for drag and drop.
Has it been explored to have a potential useDroppable hook that doesn鈥檛 set up context but instead registers with the top-level context so that it can then be consumed by useDraggable.
Ok, this will be my last attempt with useProvider returning a function which can setup context for a subtree:
useProvider:
function useProvider (Context, value) {
return (children) => {
return (
<Context.Provider value={value}>{children}</Context.Provider>
)
}
}
useDroppable:
function useDroppable ({ id }) {
const droppable = useProvider(DroppableContext, { ... })
...
return [droppable, droppableProvided, droppableSnapshot]
}
useDraggable:
funciton useDraggable ({ id }) {
const { fromContext } = useContext(DroppableContext)
...
return [draggable, draggableProvided, draggableSnapshot]
}
Usage:
import { useState } from 'react'
import { DragDropContext, useDroppable, useDraggable } from 'react-beautiful-dnd'
export default function App () {
const [items, setItems] = useState(['one', 'two', 'three'])
function onDragEnd ({ type, reason, destination, source }) {
if (type === 'DEFAULT' && reason === 'DROP' && destination) {
const newItems = Array.from(items)
const moved = newItems.splice(source.index, 1)
newItems.splice(destination.index, 0, moved)
setItems(newItems)
}
}
const [droppable, droppableProvided, droppableSnapshot] = useDroppable({
id: 'dropzone'
})
return (
<DragDropContext onDragEnd={onDragEnd}>
{droppable(
<ul ref={droppableProvided.innerRef}>
{items.map((item, idx) => {
const [draggable, draggableProvided, draggableSnapshot] = useDraggable({
id: `draggable-${item}`,
index: idx
})
return draggable(
<li
key={item}
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>{item}</li>
)
})}
{droppableProvided.placeholder}
</ul>
)}
</DragDropContext>
)
}
Working example of useProvider
I don't think this changes the conclusion for this repo, but it may be interesting for people following this discussion.
So what is actual status of this? I can see Hooks PR merged, but I don't really see it in Docs etc.
@pie6k
So what is actual status of this? I can see Hooks PR merged, but I don't really see it in Docs etc.
As far as I know there is no user facing hooks API yet because react-beautiful-dnd uses react context and there is no way for a hook to set up a context. For that you need to render a ContextProvider which ultimately means you need a component anyways, at least for a Droppable.
The PR only replaced some internal logic that has been managed in class based components with hooks.
Most helpful comment
It then turns into a hot mess when you start to drag