├── 01-react-course-introduction-beautiful-and-accessible-drag-and-drop-with-react-beautiful-dnd.md ├── 02-react-set-up-a-react-environment-with-create-react-app.md ├── 03-react-create-and-style-a-list-of-data-with-react.md ├── 04-react-reorder-a-list-with-react-beautiful-dnd.md ├── 05-react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback.md ├── 06-react-customise-the-appearance-of-an-app-during-a-drag-using-react-beautiful-dnd-snapshot-values.md ├── 07-react-customise-the-appearance-of-an-app-using-react-beautiful-dnd-ondragstart-and-ondragend.md ├── 08-react-designate-control-of-dragging-for-a-react-beautiful-dnd-draggable-with-draghandleprops.md ├── 09-react-move-items-between-columns-with-react-beautiful-dnd-using-ondragend.md ├── 10-react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props.md ├── 11-react-create-reorderable-horizontal-lists-with-react-beautiful-dnd-direction-prop.md ├── 12-react-reorder-columns-with-react-beautiful-dnd.md ├── 13-react-optimize-performance-in-react-beautiful-dnd-with-shouldcomponentupdate-and-purecomponent.md ├── 14-react-customize-screen-reader-messages-for-drag-and-drop-with-react-beautiful-dnd.md └── README.md /01-react-course-introduction-beautiful-and-accessible-drag-and-drop-with-react-beautiful-dnd.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 1](https://egghead.io/lessons/react-course-introduction-beautiful-and-accessible-drag-and-drop-with-react-beautiful-dnd) 2 | 3 | No code was done in this lesson. Just a quick overview of what is going to go on in the course. -------------------------------------------------------------------------------- /02-react-set-up-a-react-environment-with-create-react-app.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 2](https://egghead.io/lessons/react-set-up-a-react-environment-with-create-react-app) 2 | 3 | Went though this lesson and everything worked just fine. 4 | 5 | Ran `create-react-app task-app` to create a simple react application and named our folder `task-app`. Reduced the `index.js` file. 6 | 7 | ```js 8 | import React from 'react'; 9 | import ReactDOM from 'react-dom'; 10 | 11 | const App = () => 'Hello World' 12 | 13 | ReactDOM.render(, document.getElementById('root')); 14 | ``` 15 | 16 | We also deleted every other file in the `src` folder except for our index.js file. 17 | -------------------------------------------------------------------------------- /03-react-create-and-style-a-list-of-data-with-react.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 3](https://egghead.io/lessons/react-create-and-style-a-list-of-data-with-react) 2 | 3 | Created a file inside of `src` called `initial-data.js` where we are going to hold all of our static data, sort and name each task, separate them into columns, and export them. 4 | 5 | ```JS 6 | const initialData = { 7 | tasks: { 8 | 'task-1': { id: 'task-1', content: 'Take out the garbage' }, 9 | 'task-2': { id: 'task-2', content: 'Watch my favorite show' }, 10 | 'task-3': { id: 'task-3', content: 'Charge my phone' }, 11 | 'task-4': { id: 'task-4', content: 'Cook dinner' }, 12 | }, 13 | columns: { 14 | 'column-1': { 15 | id: 'column-1', 16 | title: 'To do', 17 | taskIds: ['task-1', 'task-2', 'task-3', 'task-4'], 18 | } 19 | }, 20 | // Facilitate reordering of the columns 21 | columnOrder: [ 'column-1'] 22 | }; 23 | 24 | export default initialData; 25 | ``` 26 | 27 | Then we import that file into our `index.js` and have it return our data. 28 | 29 | ```js 30 | import initialData from './initial-data'; 31 | 32 | class App extends React.Component { 33 | state = initialData 34 | render() { 35 | return this.state.columnOrder.map((columnId) => { 36 | const column = this.state.columns[columnId]; 37 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 38 | 39 | return column.title; 40 | }) 41 | } 42 | } 43 | ``` 44 | 45 | Now instead of just returning the `column.title`, we are going to return a column with all of the tasks in that column. 46 | 47 | ```js 48 | return ; 49 | ``` 50 | 51 | We don't currently have this created yet, so let's do that. 52 | 53 | We create `column.js` inside of our `src` folder and just have it render out the title again to make sure it's working. 54 | 55 | ```js 56 | import React from 'react'; 57 | 58 | export default class column extends React.Component { 59 | render() { 60 | return this.props.column.title; 61 | } 62 | } 63 | ``` 64 | 65 | Now is where we have to start styling the application. We are going to install a package called `styled-components`. 66 | 67 | Also going to install a css reset, `yarn add @atlaskit/css-reset`, for improving the visual consistency between browsers. Let's import that into our index.js file. If you import that, you'll see a slight difference to your web output. 68 | 69 | Now we add a little styling to this to make it look nicer. Just a margin and padding to our container, title, and tasklist. 70 | 71 | ```js 72 | const Container = styled.div` 73 | margin: 8px; 74 | border: 1px solid lightgrey; 75 | border-radius: 2px 76 | `; 77 | const Title = styled.h3` 78 | padding: 8px; 79 | `; 80 | const TaskList = styled.div` 81 | padding: 8px; 82 | `; 83 | ``` 84 | 85 | Now we make our task list return a list of tasks. 86 | ```js 87 | 88 | {this.props.column.title} 89 | 90 | {this.props.tasks.map(task => )} 91 | 92 | 93 | ``` 94 | 95 | But we need to create our `Task`. 96 | 97 | We create our task file in the `src` folder. and have it just print out the contents of our tasks. 98 | 99 | ```js 100 | import React from 'react'; 101 | 102 | export default class Task extends React.Component { 103 | render() { 104 | return this.props.task.content 105 | } 106 | } 107 | ``` 108 | 109 | I ran into an error here. for some reason it didn't like that `column.jsx` and `task.jsx` were both `jsx`. If I removed the `jsx` on column, the app works just fine. 110 | 111 | We then add in some styling to separate out all of the different tasks. 112 | 113 | ```js 114 | import React from 'react'; 115 | import styled from 'styled-components'; 116 | 117 | const Container = styled.div` 118 | border: 1px solid lightgrey; 119 | border-radius: 2px; 120 | padding: 8px; 121 | margin-bottom: 8px; 122 | `; 123 | 124 | export default class Task extends React.Component { 125 | render() { 126 | return {this.props.task.content} 127 | } 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /04-react-reorder-a-list-with-react-beautiful-dnd.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 4](https://egghead.io/lessons/react-reorder-a-list-with-react-beautiful-dnd) 2 | 3 | We start by downloading and adding `react-beautiful-dnd` to our `package.json` and wrap our column in `index.js` with a `Container`. 4 | On Container, we only want one thing, `onDragEnd` which we are going to leave blank for now. 5 | 6 | ```js 7 | class App extends React.Component { 8 | state = initialData 9 | 10 | onDragEnd = result => { 11 | 12 | } 13 | 14 | render() { 15 | return ( 16 | 19 | {this.state.columnOrder.map((columnId) => { 20 | const column = this.state.columns[columnId]; 21 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 22 | 23 | return ; 24 | })} 25 | 26 | ) 27 | } 28 | } 29 | ``` 30 | 31 | Now we head over to `column.js` to work in that file. We first `import {Droppable} from 'react-beautiful-dnd'` and then wrap our `TaskList` with the Droppable. Droppable only takes one required prop, a `droppableId`. This need to have a unique ID which we are going to use each column's id. 32 | 33 | ```js 34 | import {Droppable} from 'react-beautiful-dnd'; 35 | 36 | export default class Column extends React.Component { 37 | render() { 38 | return ( 39 | 40 | {this.props.column.title} 41 | 42 | 43 | {this.props.tasks.map(task => )} 44 | 45 | 46 | 47 | ); 48 | } 49 | } 50 | ``` 51 | 52 | Now when wrapping our TaskList with a Droppable, we get an error: `children is not a function`. The Droppable utilizes the Render Props pattern and expects its child to be a function that returns a react component. To fix this, we have to put our TaskList inside of a function. 53 | 54 | ```js 55 | 56 | {() => 57 | 58 | {this.props.tasks.map(task => )} 59 | 60 | } 61 | 62 | ``` 63 | 64 | `provided` is our first prop in our function. This will give us things like `droppableProps` which we will use to designate which component we want as our droppable. 65 | 66 | ```js 67 | export default class Column extends React.Component { 68 | render() { 69 | return ( 70 | 71 | {this.props.column.title} 72 | 73 | {(provided) => 74 | 77 | {this.props.tasks.map(task => )} 78 | 79 | } 80 | 81 | 82 | ); 83 | } 84 | } 85 | ``` 86 | 87 | The `placeholder` needs to be added as a child of the component that you designate as the droppable. This concludes the droppable. 88 | 89 | ```js 90 | 94 | {this.props.tasks.map(task => )} 95 | {provided.placeholder} 96 | 97 | ``` 98 | 99 | Now we head over to `task.jsx` to make our tasks draggable. We `import { Draggable } from 'react-beautiful-dnd';` and wrap our return with `` component. Draggable takes two props, `draggableId` which we will pass in our `task.id`, and index. 100 | 101 | ```js 102 | export default class Task extends React.Component { 103 | render() { 104 | 105 | return {this.props.task.content} 106 | 107 | } 108 | } 109 | ``` 110 | 111 | We are currently not passing an index to our task component so we will head over to our column component to do that. 112 | 113 | ```js 114 | 118 | {this.props.tasks.map((task, index) => )} 119 | {provided.placeholder} 120 | 121 | ``` 122 | 123 | Now we head back over into `task.jsx` and put our child component into a function. 124 | 125 | ```jsx 126 | export default class Task extends React.Component { 127 | render() { 128 | return ( 129 | 130 | {(provided) => ( 131 | {this.props.task.content} 132 | )} 133 | 134 | ) 135 | } 136 | } 137 | ``` 138 | 139 | We handle this basically like we did in the column. 140 | 141 | ```js 142 | export default class Task extends React.Component { 143 | render() { 144 | return ( 145 | 146 | {(provided) => ( 147 | {this.props.task.content} 152 | )} 153 | 154 | ) 155 | } 156 | } 157 | ``` 158 | 159 | :warning: `innerRef` Deprecation :warning: 160 | 161 | Here we run into a deprecation. I wasn't able to drag and drop like the instructor was able to. To fix this issue, simply change `innerRef` inside of `column.js` and `task.jsx` to just `ref` and it'll work perfectly. I noticed this looking at this :thinking: [article](https://medium.com/@reireynoso/drag-ndrop-with-react-beautiful-dnd-73014e5937f2) and seeing the subtle difference. :thinking: [Here](https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/using-inner-ref.md) some more documentation on how to use innerRef. 162 | 163 | 164 | Here is my complete column.js and task.jsx files at this point. 165 | #### column.js 166 | ```js 167 | import React from 'react'; 168 | import styled from 'styled-components'; 169 | import { Droppable } from 'react-beautiful-dnd'; 170 | import Task from './task'; 171 | 172 | const Container = styled.div` 173 | margin: 8px; 174 | border: 1px solid lightgrey; 175 | border-radius: 2px; 176 | `; 177 | const Title = styled.h3` 178 | padding: 8px; 179 | `; 180 | const TaskList = styled.div` 181 | padding: 8px; 182 | `; 183 | 184 | export default class Column extends React.Component { 185 | render() { 186 | return ( 187 | 188 | {this.props.column.title} 189 | 190 | {provided => 191 | 195 | {this.props.tasks.map((task, index) => ( 196 | 197 | ))} 198 | {provided.placeholder} 199 | 200 | } 201 | 202 | 203 | ); 204 | } 205 | } 206 | ``` 207 | 208 | #### task.jsx 209 | ```jsx 210 | import React from 'react'; 211 | import styled from 'styled-components'; 212 | import { Draggable } from 'react-beautiful-dnd'; 213 | 214 | const Container = styled.div` 215 | border: 1px solid lightgrey; 216 | border-radius: 2px; 217 | padding: 8px; 218 | margin-bottom: 8px; 219 | background-color: green; 220 | `; 221 | 222 | export default class Task extends React.Component { 223 | render() { 224 | return ( 225 | 226 | {provided => ( 227 | 232 | {this.props.task.content} 233 | 234 | )} 235 | 236 | ); 237 | } 238 | } 239 | ``` 240 | -------------------------------------------------------------------------------- /05-react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 5](https://egghead.io/lessons/react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback) 2 | 3 | When we move things around in our task list, it doesn't save and the tasks go back to their original position. 4 | 5 | Inside of our `onDragEnd` function in `index.js`, we already passed in `result`. We not want to grab off the different props we want from result, destination, source, and draggableId. Then we will do a couple checks to see where the task was dropped. 6 | 7 | ```js 8 | onDragEnd = result => { 9 | const {destination, source, draggableId } = result; 10 | 11 | if(!destination) { 12 | return; 13 | } 14 | 15 | if (destination.droppableId === source.droppableId && destination.index === source.index) { 16 | return; 17 | } 18 | 19 | const column = this.state.columns[source.droppableId]; 20 | const newTaskIds = Array.from(column.taskIds); 21 | newTaskIds.splice(source.index, 1); 22 | newTaskIds.splice(destination.index, 0, draggableId); 23 | 24 | const newColumn = { 25 | ...column, 26 | taskIds: newTaskIds, 27 | }; 28 | 29 | 30 | }; 31 | ``` 32 | 33 | We create a new column and a new state and save those in a constant. 34 | 35 | ```js 36 | onDragEnd = result => { 37 | const {destination, source, draggableId } = result; 38 | 39 | if(!destination) { 40 | return; 41 | } 42 | 43 | if (destination.droppableId === source.droppableId && destination.index === source.index) { 44 | return; 45 | } 46 | 47 | const column = this.state.columns[source.droppableId]; 48 | const newTaskIds = Array.from(column.taskIds); 49 | newTaskIds.splice(source.index, 1); 50 | newTaskIds.splice(destination.index, 0, draggableId); 51 | 52 | const newColumn = { 53 | ...column, 54 | taskIds: newTaskIds, 55 | }; 56 | 57 | const newState = { 58 | ...this.state, 59 | columns: { 60 | ...this.state.columns, 61 | [newColumn.id]: newColumn, 62 | }, 63 | }; 64 | 65 | this.setState(newState); 66 | }; 67 | ``` 68 | 69 | This now saves our tasks in their new order. 70 | -------------------------------------------------------------------------------- /06-react-customise-the-appearance-of-an-app-during-a-drag-using-react-beautiful-dnd-snapshot-values.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 6](https://egghead.io/lessons/react-customise-the-appearance-of-an-app-during-a-drag-using-react-beautiful-dnd-snapshot-values) 2 | 3 | Now we are going to be working on customizing the appearance of an app during a drag. 4 | 5 | We are going to be talking about `snapshot`. It has two main properties, `isDragging` and `draggingOver`. 6 | 7 | Let's change our tasks to be a different color when dragging them. 8 | 9 | To do that, we add `snapshot` as a parameter to our Container function and in the styling, we add the function to check that if a task is being dragged, it would change the color to light green, otherwise it would have a white background. 10 | 11 | ```js 12 | const Container = styled.div` 13 | border: 1px solid lightgrey; 14 | border-radius: 2px; 15 | padding: 8px; 16 | margin-bottom: 8px; 17 | background-color: ${props => (props.isDragging ? 'lightGreen' : 'white')}; 18 | `; 19 | 20 | export default class Task extends React.Component { 21 | render() { 22 | return ( 23 | 24 | {(provided, snapshot) => ( 25 | 31 | {this.props.task.content} 32 | 33 | )} 34 | 35 | ); 36 | } 37 | } 38 | ``` 39 | 40 | Now we can do the same thing with the column. When we are dragging a task around in a column, we can change the color of the background of that column by using `isDraggingOver`. 41 | 42 | ```js 43 | const TaskList = styled.div` 44 | padding: 8px; 45 | background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')}; 46 | `; 47 | 48 | export default class Column extends React.Component { 49 | render() { 50 | return ( 51 | 52 | {this.props.column.title} 53 | 54 | {(provided, snapshot) => 55 | 60 | {this.props.tasks.map((task, index) => ( 61 | 62 | ))} 63 | {provided.placeholder} 64 | 65 | } 66 | 67 | 68 | ); 69 | } 70 | } 71 | ``` 72 | 73 | You can also add in transitions to your color changes to make it a little more visually appealing. 74 | 75 | ```js 76 | const TaskList = styled.div` 77 | padding: 8px; 78 | transition: background-color 0.2s ease; 79 | background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')}; 80 | `; 81 | ``` -------------------------------------------------------------------------------- /07-react-customise-the-appearance-of-an-app-using-react-beautiful-dnd-ondragstart-and-ondragend.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 7](https://egghead.io/lessons/react-customise-the-appearance-of-an-app-using-react-beautiful-dnd-ondragstart-and-ondragend) 2 | 3 | We've looked at `onDragEnd` so far but there are two others, `onDragUpdate` and `onDragStart`. We can do things like change the text color when we start to drag something and have it change back to normal once the drag has ended. 4 | 5 | ```js 6 | onDragStart = () => { 7 | document.body.style.color = 'orange'; 8 | } 9 | 10 | onDragEnd = result => { 11 | document.body.style.color = 'inherit'; 12 | ... 13 | } 14 | ``` 15 | 16 | We can do the same thing when something gets updated. We can make it so that the background color changes with each update to the tasks list. 17 | 18 | ```js 19 | onDragUpdate = update => { 20 | const { destination } = update; 21 | const opacity = destination 22 | ? destination.index /Object.keys(this.state.tasks).length 23 | : 0; 24 | document.body.style.backgroundColor = `rgba(153, 141, 217, ${opacity})` 25 | } 26 | ``` 27 | 28 | We can add in transitions to the color change on update and have it revert back normal once the drag has ended. 29 | 30 | ```js 31 | onDragStart = () => { 32 | document.body.style.color = 'orange'; 33 | document.body.style.transition = 'background-color 0.2s ease'; 34 | } 35 | 36 | onDragUpdate = update => { 37 | const { destination } = update; 38 | const opacity = destination 39 | ? destination.index /Object.keys(this.state.tasks).length 40 | : 0; 41 | document.body.style.backgroundColor = `rgba(153, 141, 217, ${opacity})` 42 | } 43 | 44 | onDragEnd = result => { 45 | document.body.style.color = 'inherit'; 46 | document.body.style.backgroundColor = 'inherit'; 47 | 48 | ... 49 | } 50 | ``` -------------------------------------------------------------------------------- /08-react-designate-control-of-dragging-for-a-react-beautiful-dnd-draggable-with-draghandleprops.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 8](https://egghead.io/lessons/react-designate-control-of-dragging-for-a-react-beautiful-dnd-draggable-with-draghandleprops) 2 | 3 | The `dragHandleProps` is what is allowing us to click anywhere on our Task component to drag our tasks throughout our list. If we wanted to, we could create a little box inside of our Task box to be the only place we could click and drag our task. 4 | 5 | We do this by creating a Handle component inside of our `Draggable` container. We move `{...provided.dragHandleProps}` from our `Container` to our newly created `Handle`. We also need to create our Handle and give it some styling. 6 | 7 | ```js 8 | const Handle = styled.div` 9 | width: 20px; 10 | height: 20px; 11 | background-color: orange; 12 | border-radius: 4px; 13 | margin-right: 8px; 14 | ` 15 | 16 | export default class Task extends React.Component { 17 | render() { 18 | return ( 19 | 20 | {(provided, snapshot) => ( 21 | 26 | 29 | {this.props.task.content} 30 | 31 | )} 32 | 33 | ); 34 | } 35 | } 36 | ``` 37 | 38 | While this may be nice, it's not going to stay in the application for this course. -------------------------------------------------------------------------------- /09-react-move-items-between-columns-with-react-beautiful-dnd-using-ondragend.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 9](https://egghead.io/lessons/react-move-items-between-columns-with-react-beautiful-dnd-using-ondragend) 2 | 3 | Here we are going to be adding in two more columns, an in progress and done column. We can just quickly add those in our `initial-data.js` file. 4 | 5 | ```js 6 | const initialData = { 7 | tasks: { 8 | 'task-1': { id: 'task-1', content: 'Take out the garbage' }, 9 | 'task-2': { id: 'task-2', content: 'Watch my favorite show' }, 10 | 'task-3': { id: 'task-3', content: 'Charge my phone' }, 11 | 'task-4': { id: 'task-4', content: 'Cook dinner' }, 12 | }, 13 | columns: { 14 | 'column-1': { 15 | id: 'column-1', 16 | title: 'To do', 17 | taskIds: ['task-1', 'task-2', 'task-3', 'task-4'], 18 | }, 19 | 'column-2': { 20 | id: 'column-2', 21 | title: 'In progress', 22 | taskIds: [], 23 | }, 24 | 'column-3': { 25 | id: 'column-3', 26 | title: 'Done', 27 | taskIds: [], 28 | } 29 | }, 30 | // Facilitate reordering of the columns 31 | columnOrder: [ 'column-1', 'column-2', 'column-3'] 32 | }; 33 | 34 | export default initialData; 35 | ``` 36 | 37 | Now, over in `index.js`, we are going to wrap our columns in a `Container` and then add some styles to that container as well as import styled. 38 | 39 | ```js 40 | import styled from 'styled-components'; 41 | 42 | const Container = styled.div` 43 | display: flex; 44 | ` 45 | 46 | render() { 47 | return ( 48 | 49 | 50 | {this.state.columnOrder.map(columnId => { 51 | const column = this.state.columns[columnId]; 52 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 53 | 54 | return ; 55 | })} 56 | 57 | 58 | ) 59 | } 60 | ``` 61 | 62 | To make our columns look nicer and all have the same look, in `column.js`, we are going to add a `width: 220px;` to our Container. 63 | 64 | One thing that happens here that doesn't happen in the lesson video, is that our other columns are highlighting when we try to drag a task over into it. Now it doesn't highlight the entire column like we want it too, so we can change that by adding in some `flex` to our styling as well as a minimum height. 65 | 66 | ```js 67 | const Container = styled.div` 68 | margin: 8px; 69 | border: 1px solid lightgrey; 70 | border-radius: 2px; 71 | width: 220px; 72 | 73 | display: flex; 74 | flex-direction: column; 75 | `; 76 | 77 | const TaskList = styled.div` 78 | padding: 8px; 79 | transition: background-color 0.2s ease; 80 | background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')}; 81 | flex-grow: 1; 82 | min-height: 100px; 83 | `; 84 | ``` 85 | 86 | Now we need to update the reordering of our onDragEnd so that we can drop tasks into other columns. We need to edit our `onDragEnd` to basically create a new column and splice in our task every time we want to drag one into it. 87 | 88 | ```js 89 | onDragEnd = result => { 90 | document.body.style.color = 'inherit'; 91 | document.body.style.backgroundColor = 'inherit'; 92 | 93 | const {destination, source, draggableId } = result; 94 | 95 | if(!destination) { 96 | return; 97 | } 98 | 99 | if (destination.droppableId === source.droppableId && destination.index === source.index) { 100 | return; 101 | } 102 | 103 | const start = this.state.columns[source.droppableId]; 104 | const finish = this.state.columns[destination.droppableId]; 105 | 106 | if(start === finish) { 107 | const newTaskIds = Array.from(start.taskIds); 108 | newTaskIds.splice(source.index, 1); 109 | newTaskIds.splice(destination.index, 0, draggableId); 110 | 111 | const newColumn = { 112 | ...finish, 113 | taskIds: newTaskIds, 114 | }; 115 | 116 | const newState = { 117 | ...this.state, 118 | columns: { 119 | ...this.state.columns, 120 | [newColumn.id]: newColumn, 121 | }, 122 | }; 123 | 124 | this.setState(newState); 125 | return; 126 | } 127 | 128 | // Moving from one list to another 129 | const startTaskIds = Array.from(start.taskIds); 130 | startTaskIds.splice(source.index, 1); 131 | const newStart = { 132 | ...start, 133 | taskIds: startTaskIds, 134 | }; 135 | 136 | const finishTaskIds = Array.from(finish.taskIds); 137 | finishTaskIds.splice(destination.index, 0, draggableId); 138 | const newFinish = { 139 | ...finish, 140 | taskIds: finishTaskIds, 141 | }; 142 | 143 | const newState = { 144 | ...this.state, 145 | columns: { 146 | ...this.state.columns, 147 | [newStart.id]: newStart, 148 | [newFinish.id]: newFinish, 149 | }, 150 | }; 151 | this.setState(newState); 152 | }; 153 | ``` 154 | 155 | Don't forget to change your `column` to either `start` or `finish` and correct the logic that was using `column` anywhere else in this file. I forgot to do that and took a decent chunk of time figuring out where I missed that. 156 | 157 | Also this still works for keyboard. :thinking: [Here](https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/sensors/keyboard.md) is some documentation on that and shortcuts. -------------------------------------------------------------------------------- /10-react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 10](https://egghead.io/lessons/react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props) 2 | 3 | There is basically an on/off switch to components to allow them to be dragged or not. To do this, we use `isDragDisabled`. 4 | 5 | ```js 6 | 11 | ``` 12 | 13 | We can make it so that there are conditions to wether or not a task can be dragged. We will make a new const and assign our conditions to that. 14 | 15 | ```js 16 | const isDragDisabled = this.props.task.id === 'task-1'; 17 | ``` 18 | 19 | Now we can assign `isDragDisabled` to `isDragDisabled` with our conditions, not allowing dragging if the id is equal to 'task-1'. 20 | 21 | ```js 22 | 27 | {(provided, snapshot) => ( 28 | 35 | {this.props.task.content} 36 | 37 | )} 38 | 39 | ``` 40 | 41 | We can add styling to show wether or not a task is draggable. 42 | 43 | ```js 44 | const Container = styled.div` 45 | border: 1px solid lightgrey; 46 | border-radius: 2px; 47 | padding: 8px; 48 | margin-bottom: 8px; 49 | background-color: ${props => 50 | props.isDragDisabled 51 | ?'lightgrey' 52 | : props.isDragging 53 | ? 'lightGreen' 54 | : 'white'}; 55 | `; 56 | ``` 57 | 58 | There are two mechanisms that control where a draggable can be dropped. the simplest is the droppable `type`. We can set our conditions to wether or not a task can be dropped into a certain column. We can add our stipulations in `column.js`. 59 | 60 | ```js 61 | 65 | ``` 66 | 67 | With this, we set the 3rd column to need a type of `done` to have a task dropped in it and the other two need to have a type of `active` to be dropped in. 68 | 69 | The second mechanism is the droppable `isDropDisabled`. 70 | 71 | ```js 72 | 76 | ``` 77 | 78 | We can make it so that tasks can only be dropped to the right of where they started. Over in `index.js`, we create an `onDragStart` function to hold most of our logic for this. 79 | 80 | ```js 81 | onDragStart = start => { 82 | const homeIndex = this.state.columnOrder.indexOf(start.source.droppableId); 83 | 84 | this.setstate({ 85 | homeIndex, 86 | }); 87 | }; 88 | ``` 89 | 90 | Add in this: 91 | 92 | ```js 93 | this.setState({ 94 | homeIndex: null, 95 | }) 96 | ``` 97 | 98 | to our `onDragEnd` function. 99 | 100 | We now bind our `onDragStart` function to `theDragDropContext` at the bottom of our file. 101 | 102 | ```js 103 | 107 | ``` 108 | 109 | We are going to get the `index` of the column from our map function as well as creating a function called `isDropDisabled`. It will be set to true when the index of our map function is less than the index of the column that we started on. 110 | 111 | ```js 112 | {this.state.columnOrder.map((columnId, index) => { 113 | const column = this.state.columns[columnId]; 114 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 115 | 116 | const isDropDisabled = index < this.state.homeIndex; 117 | 118 | return ; 119 | })} 120 | ``` 121 | 122 | This will prevent dragging backwards. 123 | 124 | Now I ran into an issue, I was still able to drag my tasks backwards, that was because I didn't delete our previous `onDragStart` function that we made. Make sure to delete that function before creating our new one. 125 | 126 | ```js 127 | onDragStart = () => { 128 | document.body.style.color = 'orange'; 129 | document.body.style.transition = 'background-color 0.2s ease'; 130 | } 131 | ``` 132 | 133 | Delete this ^ -------------------------------------------------------------------------------- /11-react-create-reorderable-horizontal-lists-with-react-beautiful-dnd-direction-prop.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 11](https://egghead.io/lessons/react-create-reorderable-horizontal-lists-with-react-beautiful-dnd-direction-prop) 2 | 3 | We are going to start this lesson off by deleting our second and third column and removing their id's from the columnOrder in `initial-data.js`. 4 | 5 | ```js 6 | const initialData = { 7 | tasks: { 8 | 'task-1': { id: 'task-1', content: 'Take out the garbage' }, 9 | 'task-2': { id: 'task-2', content: 'Watch my favorite show' }, 10 | 'task-3': { id: 'task-3', content: 'Charge my phone' }, 11 | 'task-4': { id: 'task-4', content: 'Cook dinner' }, 12 | }, 13 | columns: { 14 | 'column-1': { 15 | id: 'column-1', 16 | title: 'To do', 17 | taskIds: ['task-1', 'task-2', 'task-3', 'task-4'], 18 | } 19 | }, 20 | // Facilitate reordering of the columns 21 | columnOrder: [ 'column-1'] 22 | }; 23 | 24 | export default initialData; 25 | ``` 26 | 27 | Now let's remove some of the styling that we did for our tasks in `column.js`. 28 | 29 | ```js 30 | const Container = styled.div` 31 | margin: 8px; 32 | border: 1px solid lightgrey; 33 | border-radius: 2px; 34 | 35 | display: flex; 36 | flex-direction: column; 37 | `; 38 | const Title = styled.h3` 39 | padding: 8px; 40 | `; 41 | const TaskList = styled.div` 42 | padding: 8px; 43 | transition: background-color 0.2s ease; 44 | background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')}; 45 | 46 | display: flex; 47 | `; 48 | ``` 49 | 50 | And lets have it print out only the first character of our tasks. We do this very easily in our `task.jsx` file. 51 | 52 | ```js 53 | 59 | {this.props.task.content[0]} 60 | 61 | ``` 62 | 63 | Let's add a width and a height to our boxes as well as making them round and centering the text. 64 | 65 | ```js 66 | const Container = styled.div` 67 | border: 1px solid lightgrey; 68 | border-radius: 50%; 69 | padding: 8px; 70 | margin-right: 8px; 71 | background-color: ${props => props.isDragging ? 'lightGreen' : 'white'}; 72 | width: 40px; 73 | height: 40px; 74 | 75 | display: flex; 76 | justify-content: center; 77 | align-items: center; 78 | `; 79 | ``` 80 | 81 | Now we really need to set it up so that we can drag in between our tasks and make that look much nicer. 82 | 83 | In the Droppable component in `column.js`, you can add in the prop `direction`. By default, it is set to 'vertical'. Let's set it to horizontal. 84 | 85 | ```js 86 | 91 | ``` 92 | 93 | Now we can drag from right to left and it works with keyboard. 94 | 95 | Let's change our border color to red when a task is focused on as well as increase our border size. 96 | 97 | ```js 98 | const Container = styled.div` 99 | border: 3px solid lightgrey; 100 | border-radius: 50%; 101 | padding: 8px; 102 | margin-right: 8px; 103 | background-color: ${props => props.isDragging ? 'lightGreen' : 'white'}; 104 | width: 40px; 105 | height: 40px; 106 | 107 | display: flex; 108 | justify-content: center; 109 | align-items: center; 110 | 111 | &:focus { 112 | outline: none; 113 | border-color: red; 114 | } 115 | `; 116 | ``` -------------------------------------------------------------------------------- /12-react-reorder-columns-with-react-beautiful-dnd.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 12](https://egghead.io/lessons/react-reorder-columns-with-react-beautiful-dnd) 2 | 3 | Now we are going to work on being able to reorder our columns. 4 | 5 | Let's start by removing the third column from our `initial-data.js` just to make it easier to work with. 6 | 7 | In `index.js`, Lets import Droppabled from react-beautiful-dnd. 8 | 9 | Let's wrap the Container with our new Droppable. as well as give our droppable and ID and a direction of "horizontal". We are also going to give it a type of "column". 10 | 11 | ```js 12 | 13 | 14 | {() => ( 15 | 16 | {this.state.columnOrder.map(columnId => { 17 | const column = this.state.columns[columnId]; 18 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 19 | 20 | return ; 21 | })} 22 | 23 | )} 24 | 25 | 26 | ``` 27 | 28 | Now to our Container, we need to add the droppable props as well as the DOM ref. 29 | 30 | ```js 31 | 32 | 37 | {provided => ( 38 | 42 | {this.state.columnOrder.map(columnId => { 43 | const column = this.state.columns[columnId]; 44 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 45 | 46 | return ; 47 | })} 48 | {provided.placeholder} 49 | 50 | )} 51 | 52 | 53 | ``` 54 | 55 | Now, over in `column.jsx`, we want to make our column draggable. So lets import Draggable from react-beautiful-dnd. We wrap our column inside of a Draggable and add our container to the function. For the draggableId, we are going to use the column id and make sure to give our columns and index. 56 | 57 | ```js 58 | export default class Column extends React.Component { 59 | render() { 60 | return ( 61 | 62 | {() => ( 63 | 64 | {this.props.column.title} 65 | 66 | {(provided, snapshot) => 67 | 72 | {this.props.tasks.map((task, index) => ( 73 | 74 | ))} 75 | {provided.placeholder} 76 | 77 | } 78 | 79 | 80 | )} 81 | 82 | ); 83 | } 84 | } 85 | ``` 86 | 87 | To give it an index, we have to do that over in `index.js`. We add `index` as a prop for our map function and pass that straight along to our column component. 88 | 89 | ```js 90 | {this.state.columnOrder.map((columnId, index) => { 91 | const column = this.state.columns[columnId]; 92 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 93 | 94 | return ; 95 | })} 96 | ``` 97 | 98 | Now back in `column.js`, we need to finish wiring up our draggable. We've done that a couple times now so I'm just going to paste in the code. 99 | 100 | ```js 101 | export default class Column extends React.Component { 102 | render() { 103 | return ( 104 | 105 | {provided => ( 106 | 110 | {this.props.column.title} 111 | 112 | {(provided, snapshot) => 113 | 118 | {this.props.tasks.map((task, index) => ( 119 | 120 | ))} 121 | {provided.placeholder} 122 | 123 | } 124 | 125 | 126 | )} 127 | 128 | ); 129 | } 130 | } 131 | ``` 132 | 133 | In this instance, we are going to use the title as our dragHandle. 134 | 135 | Now we can drag our columns, BUT we get an error when we drop it. Now we need to update our reordering object in onDragEnd in `index.js`. 136 | 137 | We add in the `type` prop so we can differentiate the difference between a task and a column. We add the logic for if a type equals column, it'll create a new array and delete the old one. 138 | 139 | ```js 140 | if(type === 'column') { 141 | const newColumnOrder = Array.from(this.state.columnOrder); 142 | newColumnOrder.splice(source.index, 1); 143 | newColumnOrder.splice(destination.index, 0, draggableId); 144 | } 145 | ``` 146 | 147 | We create a newState object with the columnOrder added to it and set the state to the newState. 148 | 149 | ```js 150 | if(type === 'column') { 151 | const newColumnOrder = Array.from(this.state.columnOrder); 152 | newColumnOrder.splice(source.index, 1); 153 | newColumnOrder.splice(destination.index, 0, draggableId); 154 | 155 | const newState = { 156 | ...this.state, 157 | columnOrder: newColumnOrder, 158 | }; 159 | this.setState(newState); 160 | return; 161 | } 162 | ``` 163 | 164 | We can now reorder our columns. Now lets just add some styling to our `column.js`. 165 | 166 | ```js 167 | const Container = styled.div` 168 | margin: 8px; 169 | border: 1px solid lightgrey; 170 | background-color: white; 171 | border-radius: 2px; 172 | width: 220px; 173 | 174 | display: flex; 175 | flex-direction: column; 176 | `; 177 | const Title = styled.h3` 178 | padding: 8px; 179 | `; 180 | const TaskList = styled.div` 181 | padding: 8px; 182 | transition: background-color 0.2s ease; 183 | background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'inherit')}; 184 | flex-grow: 1; 185 | min-height: 100px; 186 | `; 187 | ``` -------------------------------------------------------------------------------- /13-react-optimize-performance-in-react-beautiful-dnd-with-shouldcomponentupdate-and-purecomponent.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 13](https://egghead.io/lessons/react-optimize-performance-in-react-beautiful-dnd-with-shouldcomponentupdate-and-purecomponent) 2 | 3 | For this lesson, we are going to be spending a good bit of time on the browser using dev tools and their Highlight Updates feature. As it so happens, in an update for dev tools last year, this feature was taken out. A comparable application is the `Profiler` tab in dev tools. Hit record and then do your thing. It will record the actions taken on the browser as well as all of the different interactions between components and what happens behind the scenes. Using this will get you basically the same output. 4 | 5 | 6 | We start by making the background of our columns to lightgrey for this lesson. 7 | 8 | ```js 9 | const TaskList = styled.div` 10 | padding: 8px; 11 | transition: background-color 0.2s ease; 12 | background-color: ${props => (props.isDraggingOver ? 'lightgrey' : 'inherit')}; 13 | flex-grow: 1; 14 | min-height: 100px; 15 | `; 16 | ``` 17 | 18 | We are going to streamline things a bit. Whenever you drag a task over a droppable, it highlights that droppable and will rerender all of the tasks. We don't want this as it can slow down our application. 19 | 20 | Let's fix this by replacing our map function in our column with a new component called `InnerList`. 21 | 22 | ```js 23 | 28 | 29 | {provided.placeholder} 30 | 31 | ``` 32 | 33 | Now let's create that class. 34 | 35 | ```js 36 | class InnerList extends React.Component { 37 | render() { 38 | return this.props.tasks.map((task, index) => ( 39 | 40 | )); 41 | } 42 | } 43 | ``` 44 | 45 | We then add a `shouldComponentUpdate` life cycle method. In here, we are going to skip a render if the new tasks array shares referential equality with the existing tasks array. 46 | 47 | ```js 48 | class InnerList extends React.Component { 49 | shouldComponentUpdate(nextProps) { 50 | if (nextProps.tasks === this.props.tasks) { 51 | return false; 52 | } 53 | return true; 54 | } 55 | 56 | render() { 57 | return this.props.tasks.map((task, index) => ( 58 | 59 | )); 60 | } 61 | } 62 | ``` 63 | 64 | Now we have the tasks in a single column not being rendered when one is moved until it is dropped, we need to fix them all rendering when a column is moved. 65 | 66 | We want to stop the rendering of the children of the droppable, the container, during a drag. Instead of returning a Column, we are going to return an `InnerList` component that'll render a column. 67 | 68 | ```js 69 | {this.state.columnOrder.map((columnId, index) => { 70 | const column = this.state.columns[columnId]; 71 | const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]); 72 | 73 | return ; 79 | })} 80 | ``` 81 | 82 | Now lets create that InnerList component. 83 | 84 | ```js 85 | class InnerList extends React.Component { 86 | render() { 87 | const { column, taskMap, index } = this.props; 88 | const tasks = column.taskIds.map(taskId => taskMap[taskId]); 89 | return ; 90 | } 91 | } 92 | ``` 93 | 94 | This currently does the exact same as before. Now we add in a `shouldComponentUpdate` and add in the logic to check to see if there is a render necessary. 95 | 96 | ```js 97 | class InnerList extends React.Component { 98 | shouldComponentUpdate(nextProps) { 99 | if ( 100 | nextProps.column === this.props.column && 101 | nextPropsProps.taskMap === this.props.taskMap && 102 | nextProps.index === this.props.index 103 | ) { 104 | return false; 105 | } 106 | return true; 107 | } 108 | 109 | render() { 110 | const { column, taskMap, index } = this.props; 111 | const tasks = column.taskIds.map(taskId => taskMap[taskId]); 112 | return ; 113 | } 114 | } 115 | ``` 116 | 117 | None of these should change when a column is dragged so a render shouldn't happen. To shorten this up, we change `React.Component` to `React.PureComponent` and it'll do everything inside of that `shouldComponentUpdate` for us. 118 | 119 | ```js 120 | class InnerList extends React.PureComponent { 121 | render() { 122 | const { column, taskMap, index } = this.props; 123 | const tasks = column.taskIds.map(taskId => taskMap[taskId]); 124 | return ; 125 | } 126 | } 127 | ``` -------------------------------------------------------------------------------- /14-react-customize-screen-reader-messages-for-drag-and-drop-with-react-beautiful-dnd.md: -------------------------------------------------------------------------------- 1 | ## :movie_camera: [Lesson 14](https://egghead.io/lessons/react-customize-screen-reader-messages-for-drag-and-drop-with-react-beautiful-dnd) 2 | 3 | In this lesson, we are going to be talking about customizing the screen reader and its messages. 4 | 5 | On a mac, the way to turn screen reader on is to go to your `System Preferences`, click on `Accessibility`, click on `VoiceOver` on the left of the window, then click the `Enable VoiceOver` button. The shortcut for this is `CMD+F5`. 6 | 7 | On a windows 10 device, there are three way to turn this option on. I'm just going to link it :thinking: [here](https://support.microsoft.com/en-us/help/4028598/windows-10-start-or-stop-narrator) and you can follow those instructions. 8 | 9 | This lesson is a good example for how you can change messages but the examples that he used are already implemented in the VoiceOver. 10 | 11 | You can customize what the voice over says by using the `aria-roledescription` prop in your container. We are doing this in `task.jsx`. 12 | 13 | ```js 14 | export default class Task extends React.Component { 15 | render() { 16 | return ( 17 | 21 | {(provided, snapshot) => ( 22 | 29 | {this.props.task.content} 30 | 31 | )} 32 | 33 | ); 34 | } 35 | } 36 | ``` 37 | 38 | We can also change what it says while dragging a task. In `index.js`, we are going to add in an `onDragStart` and `onDragEnd`. 39 | 40 | ```js 41 | render() { 42 | return ( 43 | 48 | 53 | {provided => ( 54 | 58 | {this.state.columnOrder.map((columnId, index) => { 59 | const column = this.state.columns[columnId]; 60 | 61 | return ; 67 | })} 68 | {provided.placeholder} 69 | 70 | )} 71 | 72 | 73 | ) 74 | } 75 | ``` 76 | 77 | We also need to create those. These come with a prop called `provided` and on provided, we have `announce`. Here is where you can change the text. 78 | 79 | ```js 80 | onDragStart = (start, provided) => { 81 | provided.announce(`You have lifted the task in position ${start.source.index + 1}`,) 82 | } 83 | ``` 84 | 85 | For `onDragUpdate`, we have it so that it tells us the position of the task or if it is not currently over a droppable area using the `announce` method like onDragStart. 86 | 87 | ```js 88 | onDragUpdate = (update, provided) => { 89 | const message = update.description 90 | ? `You have moved the task to position ${update.destination.index + 1}` 91 | : `You are currently not over a droppable area`; 92 | 93 | provided.announce(message) 94 | } 95 | ``` 96 | 97 | Lastly for our `onDragEnd`, we can use `destination` to tell our user where they have dropped the task, either in a new position on in the starting position. 98 | 99 | ```js 100 | const message = result.destination 101 | ? `You have moved the task from position ${result.source.index + 1} to ${result.destination.index +1}` 102 | : `The task has been returned to its starting position of ${result.source.index + 1}`; 103 | 104 | provided.announce(message) 105 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Beautiful and Accessible Drag and Drop with react-beautiful-dnd

2 | 3 |

4 | 5 | # My Take 6 | 7 | This course is a bit old by a couple years, and is still very relevant. I only ran into one small deprecation in lesson 4 and I listed more documentation there about it. `innerRef` being changed to `ref`. 8 | 9 | I highly recommend making a commit after each lesson because at the start of some lessons, we will disregard everything done in the previous lesson and you will have to try to figure out where to start your code at. Making these commits will save you a good bit of time. 10 | 11 | I really enjoyed taking this course. It was a lot of fun learning all the different aspects that the instructor taught. I haven't taken a course like this and learning about an app that I can drag and drop and reorder things was a lot of fun. # Beautiful-and-Accessible-Drag-and-Drop-with-react-beautiful-dnd-notes 12 | 13 | ## Instructor 14 | 15 | - [Alex Reardon](https://egghead.io/instructors/alex-reardon) 16 | 17 | ## Description 18 | 19 | Drag and drop (dnd) experiences are often built to sort lists of content vertically and horizontally. 20 | 21 | react-beautiful-dnd is an excellent tool for these use cases. It utilizes the render props pattern to build accessible dnd functionality into lists that look and behave as you would expect—and you’ll even get keyboard-accessible dnd, straight out of the box, with no extra work required. It’s actually easy to start using, and this course is a great place to start. 22 | 23 | We will create a highly interactive task management application from scratch using the building blocks of react-beautiful-dnd. Over 14 lessons, you will get practice in: 24 | 25 | building droppable containers to sort draggable items horizontally and vertically 26 | moving items between columns 27 | and even moving the columns themselves 28 | Following the course, you’ll be ready to build powerful dnd experiences into your own projects. 29 | 30 | 31 | ## Table of Contents 32 | 33 | - [01. Course Introduction: Beautiful and Accessible Drag and Drop with react-beautiful-dnd](01-react-course-introduction-beautiful-and-accessible-drag-and-drop-with-react-beautiful-dnd.md) 34 | - [02. Set up a React Environment with create-react-app](02-react-set-up-a-react-environment-with-create-react-app.md) 35 | - [03. Create and Style a List of Data with React](03-react-create-and-style-a-list-of-data-with-react.md) 36 | - [04. Reorder a List with react-beautiful-dnd](04-react-reorder-a-list-with-react-beautiful-dnd.md) 37 | - [05. Persist List Reordering with react-beautiful-dnd using the onDragEnd Callback](05-react-persist-list-reordering-with-react-beautiful-dnd-using-the-ondragend-callback.md) 38 | - [06. Customise the Appearance of an App during a Drag using react-beautiful-dnd snapshot Values](06-react-customise-the-appearance-of-an-app-during-a-drag-using-react-beautiful-dnd-snapshot-values.md) 39 | - [07. Customise the Appearance of an App using react-beautiful-dnd onDragStart and onDragEnd](07-react-customise-the-appearance-of-an-app-using-react-beautiful-dnd-ondragstart-and-ondragend.md) 40 | - [08. Designate Control of Dragging for a react-beautiful-dnd Draggable with dragHandleProps](08-react-designate-control-of-dragging-for-a-react-beautiful-dnd-draggable-with-draghandleprops.md) 41 | - [09. Move Items between Columns with react-beautiful-dnd using onDragEnd](09-react-move-items-between-columns-with-react-beautiful-dnd-using-ondragend.md) 42 | - [10. Conditionally Allow Movement using react-beautiful-dnd Draggable and Droppable Props](10-react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props.md) 43 | - [11. Create Reorderable Horizontal Lists with react-beautiful-dnd direction prop](11-react-create-reorderable-horizontal-lists-with-react-beautiful-dnd-direction-prop.md) 44 | - [12. Reorder Columns with react-beautiful-dnd](12-react-reorder-columns-with-react-beautiful-dnd.md) 45 | - [13. Optimize Performance in react-beautiful-dnd with shouldComponentUpdate and PureComponent](13-react-optimize-performance-in-react-beautiful-dnd-with-shouldcomponentupdate-and-purecomponent.md) 46 | - [14. Customize Screen Reader Messages for Drag and Drop with react-beautiful-dnd](14-react-customize-screen-reader-messages-for-drag-and-drop-with-react-beautiful-dnd.md) 47 | 48 | 49 | | EMOJI | Definition | 50 | |:-: |--- | 51 | | :warning: | Deprecated code | 52 | | :movie_camera: | Lesson Video | 53 | | :thinking: | Resource | 54 | 55 | ## Contribute 56 | 57 | - These are community notes that I hope everyone who studies benefits from. 58 | - If you notice areas that could be improved please **feel free to open a PR**! 59 | 60 | ## Contributors ✨ 61 | 62 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 63 | 64 | 65 | 66 |

Lucas Minter

👀
67 | --------------------------------------------------------------------------------