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:
<div>
the ref
callback returns a DOM node<Person>
the ref callback returns the instance of the component - which is not what react-beautiful-dnd needsinnerRef
callback for your components to return the underlying DOM reference of the componentInstructor: [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.
You should fine this guide helpful: https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/using-inner-ref.md
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
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?
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}> ... )
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> ); } }
This is my codesandbox for this project. Please let me know if you find the root problem. https://codesandbox.io/s/o9yjz8jx1z
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.
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...
Hahaha that ref and innerRef :D
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?
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?
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?
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.
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.