Reorder a List with react-beautiful-dnd

Alex Reardon
InstructorAlex Reardon

Share this video with your friends

Send Tweet
Published 4 years ago
Updated a year ago

In this lesson, we will add react-beautiful-dnd to our project to enable reordering of our tasks.

In our lesson, we use a styled-components innerRef callback to get the DOM ref for our styled component.

If you have not used ref's in React before I recommend you check out:

A small cheatsheet about refs:

  • On a React Element such as <div> the ref callback returns a DOM node
  • On a React Component such as <Person> the ref callback returns the instance of the component - which is not what react-beautiful-dnd needs
  • It is a common practice to create your own innerRef callback for your components to return the underlying DOM reference of the component

Instructor: [00:00] We are now going to add the ability to reorder the tasks in our list using React-beautiful-dnd. Let's add React-beautiful-dnd as a dependency to our project. OK, great.

[00:20] Before going any further, let's take a visual look at the components that make up React-beautiful-dnd. React-beautiful-dnd is made up of three different components. The first is the DragDropContext. The DragDropContext is a component, that we use to wrap the part of our application, that we want to have drag and drop enable form.

[00:43] A droppable creates a region which can be dropped on to. They also contain draggables. A draggable is a component that can be dragged around and dropped into droppables. In order to enable drag and drop for our column, we're going to wrap it in a DragDropContext component.

[01:02] First, we'll import DragDropContext from React-beautiful-dnd. Then, I'm going to wrap our columns inside of a DragDropContext. A DragDropContext has three call backs. onDragStart, which is called when the drag starts. onDragUpdate is called when something changes during a drag, such as an item is moved into a new position, and onDragEnd, which is called at the end of a drag.

[01:43] The only required call back for a DragDropContext is onDragEnd. It is your responsibility of your onDragEnd function to synchronously update your state to reflect the drag and drop result. We will leave this function blank for now and come back to it soon.

[02:08] We'll now enhance our column component. We are going to import the droppable component from React-beautiful-dnd. We're then going to wrap our task list component inside of the droppable. A droppable has one required prop, a droppable ID.

[02:30] This ID needs to be unique within the DragDropContext. We're going to use the column ID. You'll see that we're getting an error in our application saying, "Children is not a function." A droppable utilizes the render props pattern and expects its child to be a function that returns a React component.

[02:58] One reason the render props pattern is used, is so that React-beautiful-dnd does not need to create any DOM nodes for you. You create your components that where you want to. React-beautiful-dnd latches into your existing structure.

[03:15] The first argument to this function is called provided. Provided is an object that serves a few important purposes. A provided object has a property for droppableProps. These are props that need to be applied to the component that you want to designate as your droppable.

[03:39] We explicitly call out what all of these props are in our documentation. You can apply each one of these individually if you want, and you are even welcome to monkey patch them. However, we won't be going into that topic here.

[03:53] Generally, you'll be able to just spread the provided.droppableProps object directly on to your component. The provided object has a property called innerRef, which is a function used to supply with DOM node of your component to React-beautiful-dnd.

[04:13] A styled component has a call back prop named innerRef, which returns the DOM node of the component. We can assign the provided.innerRef function to this prop. The last thing we'll need to do for a droppable is to insert a place holder.

[04:36] A place holder is a React element that is used to increase the available space in a droppable during a drag when it's needed. The place holder needs to be added as a child of the component that you designate as the droppable. Our droppable is now set up.

[04:57] We're going to move to our task component and make it draggable. We're going to import the draggable component from React-beautiful-dnd. Now, I'm going to wrap our container component inside of a draggable component.

[05:13] A draggable has two required props. Firstly, a draggable ID, which will assign to the task ID. Secondly, it requires an index. We're currently not passing an index to our task component. Let's go back to our column component and do that.

[05:41] Our column component is currently mapping over the task's prop and returning a task component. The second argument to a map function is the index of the item in the array. We can simply pass this index on to our task component. If we can now go back to task, we still have a problem.

[06:12] As with droppable, a draggable expects its child to be a function. The first argument to this function is a provided object, which works in a very similar way to the droppable provided object we have previously seen.

[06:32] The provided object has a property called draggableProps. These props need to be applied to the component that we want to move around in response to a user input. The provided object also has another property called dragHandleProps. These are the props that need to be applied to the part of the component that we want to use to be able to control the entire component.

[07:01] You can use this to drag a large item by just a small part of it. For our application, we want the whole task to be draggable. We're going to apply these props to the same element. As with our droppable, we also need to provide a ref for our draggable.

[07:17] Now, let's take a look at our application. We can now drag items around without mouse and without keyboard, which is pretty great. What we can see that when we are dragging an item, we can see through that item when we were dragging in, which doesn't look very great.

[07:38] Let's go back to our task. Just add a background color. Just white will be fine. When the item moves, we don't see through it. Let me go. We now have a really nice looking reordering experience.

[07:57] You might have noticed that when I drop, that reorder is not preserved. In order to preserve that reorder, we need to implement our reordering logic inside of your onDragEnd call back.

Héctor BlisS
Héctor BlisS
~ 4 years ago

Hello i have been trying this implementation several times but i still getting this error: Error: Invariant failed: Cannot get draggable ref from drag handle any insight? thanks.

Alex Reardon
Alex Reardoninstructor
~ 4 years ago

You should fine this guide helpful: https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/using-inner-ref.md

Héctor BlisS
Héctor BlisS
~ 4 years ago

I found a solution, i had to use ref={provided.innerRef} and innerRef={provided.innerRef} both in the Draggable and the Droppable components, to solve it =P

Eric Montzka
Eric Montzka
~ 4 years ago

Thanks Hector, that fixed it for me too! Btw, my react developer tools for chrome doesn't seem to work on this app for some reason. Anyone else have that problem?

IvorScott Cummings
IvorScott Cummings
~ 4 years ago

The innerRef prop has been depreciated. https://www.styled-components.com/docs/api#deprecated-innerref-prop Also see: https://github.com/atlassian/react-beautiful-dnd/issues/875

Side note (for TypeScript): If you're using TypeScript like me, you will have trouble getting TypeScript to infer the type of "provided.innerRef". However, this works:

{({ innerRef, droppableProps, placeholder }) => ( <TaskList ref={innerRef as any} {...droppableProps}> ... )

Ali Ismail
Ali Ismail
~ 4 years ago

I've tried doing this several times and keep getting this error:

A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://fb.me/react-crossorigin-error for more information.

It is while working on the column.jsx file. around 3min in. As soon as I add the droppableId="anything":

export default class Column extends React.Component { render() { return ( <Container> <Title>{this.props.column.title}</Title> <Droppable droppableId={this.props.column.id}> {this.props.tasks.map((task, index) => ( <Task key={task.id} task={task} index={index} /> ))} </Droppable> </Container> ); } }

Ali Ismail
Ali Ismail
~ 4 years ago

This is my codesandbox for this project. Please let me know if you find the root problem. https://codesandbox.io/s/o9yjz8jx1z

Dennis P
Dennis P
~ 4 years ago

I had the same error " react-dom.development.js:20781 Uncaught Error: Invariant failed: provided.innerRef has not been provided with a HTMLElement." -- Thanks Hector. Using 'ref' instead of 'innerRef' solved it for me.

Joel
Joel
~ 2 years ago

For anyone watching this tutorial now...

change: innerRef={provided.innerRef}
to: ref={provided.innerRef}

If not, you wont be able to drag the task items.

I just spent an unholy amount of time trying to figure this out...

Primata
Primata
~ 2 years ago

Hahaha that ref and innerRef :D

Alfred Ayache
Alfred Ayache
~ 2 years ago

I'm following along typing in the code myself (I should have access to the code as I'm an egghead member, but it's blocking me out), and it all seems to work, EXCEPT: moving tasks via keyboard. The video seems to show that moving them via keyboard even persists the new tasks list order. Has anyone else run into this issue?

Will Johnson
Will Johnson
~ 2 years ago

I'm following along typing in the code myself (I should have access to the code as I'm an egghead member, but it's blocking me out), and it all seems to work, EXCEPT: moving tasks via keyboard. The video seems to show that moving them via keyboard even persists the new tasks list order. Has anyone else run into this issue?

Would you happen to have a link to your code Alfred?

~ a year ago

When I completed this Lesson, my draggable items went back to their original positions. I opened your code sandbox, Alex, and the same thing occurs there. Could you take a look?

~ 6 months ago

Even when I substitute my code with yours, I get the problem below. Is this a problem with versions?

A setup problem was encountered.> Invariant failed: provided.innerRef has not been provided with a HTMLElement.