DragDropContext > Responders
Responders are top level application events that you can use to perform your own state updates, style updates, as well as to make screen reader announcements.
For more information about controlling the screen reader see our screen reader guide
onDragStart
: A drag has startedonDragUpdate
: Something has changed during a dragonDragEnd
(required): A drag has ended. It is the responsibility of this responder to synchronously apply changes that has resulted from the drag
Generally you will not need to use
onBeforeDragStart
, and it has a slightly different function signature to the rest of the responders
onBeforeDragStart
: Called just beforeonDragStart
. It is called immediately before anysnapshot
values are updated. It can be useful to do dimension locking for table reordering.
type ResponderProvided = {|
announce: Announce,
|};
type Announce = (message: string) => void;
All responders (except for onBeforeDragStart
) are provided with a second argument: ResponderProvided
. This object has one property: announce
. This function is used to synchronously announce a message to screen readers. If you do not use this function we will announce a default english message. We have created a guide for screen reader usage which we recommend using if you are interested in controlling the screen reader messages for yourself and to support internationalisation. If you are using announce
it must be called synchronously.
type OnDragStartResponder = (
start: DragStart,
provided: ResponderProvided,
) => mixed;
onDragStart
will get notified when a drag starts. This responder is optional and therefore does not need to be provided. It is highly recommended that you use this function to block updates to all Draggable
and Droppable
components during a drag. (See Block updates during a drag below)
You are provided with the following details:
type DragStart = {|
draggableId: DraggableId,
type: TypeId,
source: DraggableLocation,
mode: MovementMode,
|};
start.draggableId
: the id of theDraggable
that is now draggingstart.type
: thetype
of theDraggable
that is now draggingstart.source
: the location (droppableId
andindex
) of where the dragging item has started within aDroppable
.start.mode
: either'SNAP'
or'FLUID'
. This is a little bit of information about the type of movement that will be performed during this drag.'SNAP'
mode is where items jump around between positions (such as with keyboard dragging) and'FLUID'
mode is where the item moves underneath a pointer (such as mouse dragging).
Note: while the return type is mixed
, the return value is not used.
type OnDragStartResponder = (
start: DragStart,
provided: ResponderProvided,
) => mixed;
// supporting types
type DragStart = {|
draggableId: DraggableId,
type: TypeId,
source: DraggableLocation,
|};
type DraggableLocation = {|
droppableId: DroppableId,
// the position of the draggable within a droppable
index: number,
|};
type Id = string;
type DraggableId = Id;
type DroppableId = Id;
type TypeId = Id;
export type MovementMode = 'FLUID' | 'SNAP';
type OnDragUpdateResponder = (
update: DragUpdate,
provided: ResponderProvided,
) => mixed;
This responder is called whenever something changes during a drag. The possible changes are:
- The position of the
Draggable
has changed - The
Draggable
is now over a differentDroppable
- The
Draggable
is now over noDroppable
It is important that you not do too much work as a result of this function as it will slow down the drag. While the return type is mixed
, the return value is not used.
type DragUpdate = {|
...DragStart,
// may not have any destination (drag to nowhere)
destination: ?DraggableLocation,
// populated when a draggable is dragging over another in combine mode
combine: ?Combine,
|};
type Combine = {|
draggableId: DraggableId,
droppableId: DroppableId,
|};
...DragStart
: see aboveupdate.destination
: the location (droppableId
andindex
) of where the dragging item is now. This can be null if the user is currently not dragging over anyDroppable
.update.combine
: details of aDraggable
that is currently being combine with. For more information see our combining guide
react-beautiful-dnd
will throw an error if aonDragEnd
prop is not provided
This function is extremely important and has an critical role to play in the application lifecycle. This function must result in the synchronous reordering of a list of Draggables
It is provided with all the information about a drag:
type DropResult = {|
...DragUpdate,
reason: DropReason,
|};
type DropReason = 'DROP' | 'CANCEL';
...DragUpdate
: see aboveresult.reason
: the reason a drop occurred. This information can be helpful in crafting more useful messaging in theResponderProvided
>announce
function.
The use cases for this responder is super limited
Once we have all of the information we need to start a drag we call the onBeforeDragStart
function. This is called just before we update the snapshot
values for the Draggable
and Droppable
components. At this point the application is not in a dragging state and so changing of props such as isDropDisabled
will fail. The onBeforeDragStart
responder is a good opportunity to do any dimension locking required for table reordering.
- ✅ Can apply modifications to existing components to lock their sizes
- ❌ Cannot remove or add any
Draggable
orDroppable
- ❌ Cannot modify the sizes of any
Draggable
orDroppable
- ❌ No screen reader announcement yet
Note: while the return type is mixed
, the return value is not used.
// No second 'provided' argument
type OnBeforeDragStartResponder = (start: DragStart) => mixed;
// Otherwise the same type information as OnDragStartResponder
- User initiates a drag
- We prepare and collect information required for the drag (async). If the drag ends before this phase is completed then no responders will be fired.
onBeforeDragStart
is calledDraggable
andDroppable
components are updated with initialsnapshot
valuesonDragStart
is called in the next event loop (viasetTimeout
)
- User moves a dragging item
Draggable
andDroppable
components are updated with latestsnapshot
valuesonDragUpdate
is called in the next event loop (viasetTimeout
)
- User drops a dragging item
- There is an optional drop animation
- When the drop animation finishes (or if there is ):
-- Any pending
onDragStart
andonDragUpdate
calls are flushed --Draggable
andDroppable
components are updated with restingsnapshot
values. -- You perform your reorder operation inonDragEnd
which can result in asetState
to update the order. TheDraggable
andDroppable
snapshot updates and anysetState
caused byonDragEnd
are batched together into the render cycle byreact ⚛️
🤘
Because this library does not control your state, it is up to you to synchronously reorder your lists based on the result: DropResult
.
- if the
destination
isnull
: all done! - if
source.droppableId
equalsdestination.droppableId
you need to remove the item from your list and insert it at the correct position. - if
source.droppableId
does not equaldestination.droppableId
, then you need to remove theDraggable
from thesource.droppableId
list and add it into the correct position of thedestination.droppableId
list.
If you need to persist a reorder to a remote data store - update the list synchronously on the client (such as through this.setState()
) and fire off a request in the background to persist the change. If the remote save fails it is up to you how to communicate that to the user and update, or not update, the list.
react-beautiful-dnd
does not support the changing of the size of any Draggable
or Droppable
after a drag has started. We build a virtual model of every Draggable
and Droppable
when a drag starts. We do not recollect these during a drag. So if you change the size of something: the user will see the updated size, but our virtual model will remain unchanged.
It is highly recommended that while a user is dragging that you block any state updates that might impact the amount of Draggable
s and Droppable
s, or their dimensions. Please listen to onDragStart
and block updates to the Draggable
s and Droppable
s until you receive at onDragEnd
.
Update blocking will look different depending on how you manage your data. It is probably best to explain by example:
Let's say you are using React
component state to manage the state of your application. Your application state is tied to a REST endpoint that you poll every thirty seconds for data updates. During a drag you should not apply any server updates that could effect what is visible.
This could mean:
- stop your server poll during a drag
- ignore any results from server calls during a drag (do not call
this.setState
in your component with the new data)
Here are a few poor user experiences that can occur if you change things during a drag:
- If you increase the amount of nodes, then the library will not know about them and they will not be moved when the user would expect them to be.
- If you decrease the amount of nodes, then there might be gaps and unexpected movements in your lists.
- If you change the dimensions of any node, then it can cause the changed node as well as others to move at incorrect times.
- If you remove the node that the user is dragging, then the drag will instantly end
- If you change the dimension of the dragging node, then other things will not move out of the way at the correct time.
We try very hard to ensure that each onDragStart
event is paired with a single onDragEnd
event. However, there maybe a rogue situation where this is not the case. If that occurs - it is a bug. Currently there is no official mechanism to tell the library to cancel a current drag externally.