yarn react-beautiful-dnd 4.0.0

latest releases: 13.1.1, 13.1.0, 13.0.0...
6 years ago

Improvements

Massive performance improvements #86

This release involves some of the most comprehensive architectural changes to date - with one goal in mind: speed. Every single step of the drag life cycle has been audited and improved. The improvements are so good that everything feels much smoother to interact with.

giphy 3

Here are some figures that show the performance improvements:

Action Time before changes Time after changes Reduction
Lifting a Draggable 2600ms 15ms 99%
Dragging with small amount of displacement 9ms 6ms 33%
Dragging with large amount of displacement 9ms 6ms 33%
Moving into a large list with small amount of displacement 380ms 4ms 99%
Moving into a large list with large amount of displacement 380ms 8ms 98%
Time to start animation after a drop 370ms 4ms 99%

The figures are comprised from single and multi list configurations with a total of 500Draggables . Recording was done using development builds and with instrumentation enabled - both of which slows things down. However, the recording was also done on a fairly powerful development machine. Exact improvements will vary depending on size of data set, device performance and so on.

How did we do it? 🤔

I plan on speaking in detail about how we achieved these results at an upcoming React Sydney meetup. You can have a read about the optimisations in detail on our blog post Dragging React performance forward

Great styles out of the box

We now apply some default styles out out of the box for consumption convenience. The styles are fairly reasonable:

  • cursor: grab on the drag handle element when you can grab a drag-handle
  • cursor: grabbing on the body when you are dragging
  • user-select: none on the body when you are dragging to avoid selecting any text on the page

There is a new section in the docs which go through this in more detail

✌️ These are also vendor prefixed correctly in accordance with our supported browser matrix. We have also kept things small and still include no css-in-js library.

Obviously you are welcome to override these styles - for example you may want to use different cursors. You can use any mechanism you like to override our styles as long as they have a higher specificity (eg classes, inline styles and so on). This is the first 'opinionated' style to go into the library. Generally we want to avoid making any style decisions for you. However, given that these styles are so common, as so easy to override - we thought a reasonable default would yield the most value.

Published a recommended performance optimisation

We have added a new recommended performance optimisation for Droppables which will greatly improve the performance of moving between lists.

Other

  • Added peer dependency range for React to ^16.0.0 #249. If you are already using React 16 you will no longer get peer dep warnings. Thanks @drew-walker
  • Streamlined visibility checks #226
  • Bumped dependencies #254

Changes

In order to support our performance improvements we have needed to introduce some breaking api changes. These changes push us closer to the upcoming react 16 api

Draggable > Props

Breaking change 💥

draggableId: string
- type: string
+ index: number
// optionals
isDragDisabled?: boolean
disableInteractiveElementBlocking?: boolean

You now need to provide an index

You will now need to provide an ordered index to a Draggable. This avoids us needing to do a lot of upfront processing and also will allow us to move to a virtualised solution in the future.

Often the simplest way to grab the index is from a loop that generates the Draggable list within a Droppable.

Applied to our basic usage example:

<Droppable droppableId="droppable">
   {(provided, snapshot) => (
      <div
       ref={provided.innerRef}
       style={getListStyle(snapshot.isDraggingOver)}
      >
-     {this.state.items.map((item, index) => (
-        <Draggable key={item.id} draggableId={item.id}>
+     {this.state.items.map((item, index) => (
+        <Draggable key={item.id} draggableId={item.id} index={index}>
               {(provided, snapshot) => (
                  <div>
                    <div
                      ref={provided.innerRef}
                      {...provided.droppableProps}
                      {...provided.dragHandleProps}
                      style={getItemStyle(
                        provided.droppableProps.style,
                        snapshot.isDragging
                      )}
                    >
                      {item.content}
                    </div>
                    {provided.placeholder}
                  </div>
             )}
           </Draggable>
          ))}
        {provided.placeholder}
      </div>
    )}
</Droppable>

type no longer required

The type prop no longer needs to be provided to a Draggable. It will now be inferred from the parent Droppable. This was already the existing behaviour. However, for performance reasons we needed consumers to double provide the type. This is no longer needed. #188

Draggable > DraggableProvided

Breaking change 💥

type DraggableProvided = {|
-  draggableStyle: ?DraggableStyle,
+ draggableProps: DraggableProps,
+ // a simple rename to align with the naming of draggableProps 
-  dragHandleProps: ?DragHandleProvided,
+  dragHandleProps: ?DragHandleProps,

- These two props will be removed in an upcoming React 16 release but are currently required
  innerRef: (HTMLElement) => void,
  placeholder: ?ReactElement,
|}

+ type DraggableProps = {|
+  // inline style
+  style: ?DraggableStyle,
+  // used for shared global styles
+  'data-react-beautiful-dnd-draggable': string,
|}

type DraggableStyle = DraggingStyle | NotDraggingStyle;

- // no longer needed as these are applied via data attributes
- type BaseStyle = {|
-  WebkitTouchCallout: 'none',
-  WebkitTapHighlightColor: 'rgba(0,0,0,0)',
-  touchAction: 'manipulation',
- |}

type NotDraggingStyle = {|
- // now applied by data attribute
- ...BaseStyle,
   transform: ?string,
- transition: ?string,
+ transition: null | 'none',
- // now applied by data attribute
- pointerEvents: 'none' | 'auto',
|}

export type DraggingStyle = {|
- // now applied with data attribute
-  ...BaseStyle,
  pointerEvents: 'none',
  position: 'fixed',
  width: number,
  height: number,
  boxSizing: 'border-box',
  top: number,
  left: number,
  margin: 0,
  transform: ?string,
+ // opting out of global movement style
+ transition: 'none', 
  zIndex: ZIndex,
|}

Now setting up a draggable is as simple as:

<Draggable draggableId="my-draggable">
{(provided, snapshot) => (
  <div>
    <div 
         ref={provided.innerRef}
-       style={provided.draggableStyle}
+      {...provided.draggableProps}
        {...provided.dragHandleProps}
   >
     My Draggable!
   </div>
   {provided.placeholder}
</div>
)}
</Draggable>

When we move to React 16 #202 we will be able to drop the wrapping element, ref, and placeholder ceremony. Then all you will need to do is spread the provided.draggableProps on the element you want to be draggable. Boom.

You are still welcome to monkey patch these objects if you like. Also, now if you are using inline styles you will need to patch that correctly:

<div 
    ref={provided.innerRef}
    {...provided.draggableProps}
    {...provided.dragHandleProps}
    style={{ color: green, ...provided.draggableProps.style }}
>

You will also want to apply the inline style at after you spread provided.draggableProps so that it is not overwritten by the spread

Special thanks ❤️

This PR has been a long time coming #86. It was also a huge effort to implement #226! Thank you to those of you who have engaged with the issue and given feedback. Thanks to @seancurtis for his great css insights. Also a huge thank you to @jaredcrowe for his suggestions, brainstorming, rubber ducking and being generally encouraging.

Don't miss a new react-beautiful-dnd release

NewReleases is sending notifications on new releases.