Next.js & React-Beautiful-DnD: Drag and Drop Like a Pro

In the world of web development, creating intuitive and engaging user interfaces is paramount. One interaction that often enhances user experience is drag-and-drop functionality. Imagine being able to reorder items in a list, rearrange elements on a dashboard, or build custom layouts simply by dragging and dropping. Implementing this feature from scratch can be a complex and time-consuming task. Fortunately, libraries like react-beautiful-dnd simplify this process, allowing you to easily add drag-and-drop capabilities to your Next.js applications.

Why React-Beautiful-DnD?

react-beautiful-dnd is a React library that provides a beautiful and accessible drag-and-drop experience. It’s designed to be performant, even with large lists, and offers a smooth, natural feel. It’s also highly customizable, allowing you to tailor the drag-and-drop behavior to your specific needs. Here’s why you might choose it:

  • Accessibility: The library is built with accessibility in mind, ensuring that users with disabilities can also interact with your drag-and-drop features.
  • Performance: Optimized for performance, it handles large lists efficiently.
  • Customization: Offers a high degree of customization for styling and behavior.
  • Ease of Use: Provides a straightforward API, making it relatively easy to integrate into your projects.

Setting Up Your Next.js Project

Before diving into react-beautiful-dnd, let’s set up a basic Next.js project if you haven’t already. If you have an existing project, feel free to skip this step.

1. Create a Next.js App: Open your terminal and run the following command:

npx create-next-app my-drag-and-drop-app

This command creates a new Next.js project with the name “my-drag-and-drop-app”.

2. Navigate to Your Project: Change your directory to the newly created project:

cd my-drag-and-drop-app

3. Start the Development Server: Run the development server to see your initial Next.js app:

npm run dev

You can now view your app in your browser at http://localhost:3000.

Installing React-Beautiful-DnD

Now, let’s install the react-beautiful-dnd package. In your project’s terminal, run:

npm install react-beautiful-dnd

This command downloads and installs the necessary package to your project’s dependencies.

Basic Implementation: Reordering a List

Let’s create a simple list and implement drag-and-drop functionality to reorder the items. We’ll modify the pages/index.js file.

1. Import necessary components: Import DragDropContext, Droppable, and Draggable from react-beautiful-dnd.

import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

2. Define your data: Create an array of objects representing your list items. Each object should have a unique id.

const items = [
  { id: 'item-1', content: 'Learn React' },
  { id: 'item-2', content: 'Build a Next.js App' },
  { id: 'item-3', content: 'Implement Drag and Drop' },
  { id: 'item-4', content: 'Deploy to Vercel' },
];

3. Create a state variable: Use the useState hook to manage the order of your items. Initialize it with your initial items array.

import { useState } from 'react';

function Home() {
  const [items, setItems] = useState([
    { id: 'item-1', content: 'Learn React' },
    { id: 'item-2', content: 'Build a Next.js App' },
    { id: 'item-3', content: 'Implement Drag and Drop' },
    { id: 'item-4', content: 'Deploy to Vercel' },
  ]);

4. Implement the onDragEnd function: This function is called when a drag operation ends. It receives an object with information about the start and end positions of the dragged item. Use this information to update the state of your items array.

const onDragEnd = (result) => {
  if (!result.destination) {
    return;
  }

  const reorderedItems = Array.from(items);
  const [removed] = reorderedItems.splice(result.source.index, 1);
  reorderedItems.splice(result.destination.index, 0, removed);

  setItems(reorderedItems);
};

5. Wrap your list with DragDropContext: This component provides the context for the drag-and-drop operations. It needs the onDragEnd function as a prop.


  {/* Your Droppable and Draggable components go here */}

6. Create a Droppable area: This component defines the area where items can be dropped. It requires a droppableId, which is a unique identifier. Inside the Droppable component, use a function to render the items. This function receives a provided object, which contains props that need to be applied to the container (e.g., a <div>) to enable the drop functionality.


  {(provided) => (
    <div>
      {/* Your Draggable items go here */}
      {provided.placeholder}
    </div>
  )}

7. Create Draggable items: Wrap each list item with a Draggable component. It requires a draggableId (unique to each item) and an index (the item’s position in the list). The Draggable component also uses a function to render its content, which receives a provided object. This object contains props (e.g., ref and style) that need to be applied to the item’s container (e.g., a <div>) to enable dragging.


  {items.map((item, index) => (
    
      {(provided) => (
        <div style="{{">
          {item.content}
        </div>
      )}
    
  ))
}

8. Complete pages/index.js: Here’s the complete code for pages/index.js. This code combines all the steps above into a functional component.

import { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

function Home() {
  const [items, setItems] = useState([
    { id: 'item-1', content: 'Learn React' },
    { id: 'item-2', content: 'Build a Next.js App' },
    { id: 'item-3', content: 'Implement Drag and Drop' },
    { id: 'item-4', content: 'Deploy to Vercel' },
  ]);

  const onDragEnd = (result) => {
    if (!result.destination) {
      return;
    }

    const reorderedItems = Array.from(items);
    const [removed] = reorderedItems.splice(result.source.index, 1);
    reorderedItems.splice(result.destination.index, 0, removed);

    setItems(reorderedItems);
  };

  return (
    <div style="{{">
      <h2>Drag and Drop Example</h2>
      
        
          {(provided) => (
            <div>
              {items.map((item, index) => (
                
                  {(provided) => (
                    <div style="{{">
                      {item.content}
                    </div>
                  )}
                
              ))}
              {provided.placeholder}
            </div>
          )}
        
      
    </div>
  );
}

export default Home;

9. Run the App: Save the file and navigate to your Next.js app in your browser. You should now be able to drag and drop the list items to reorder them.

Styling and Customization

react-beautiful-dnd provides a lot of flexibility in terms of styling and customization. You can customize the appearance of the dragged item, the drop zone, and the drag handle. Here are a few examples:

Styling the Dragged Item

You can apply different styles to the dragged item while it is being dragged. The Draggable component provides the provided.draggableProps.style, which you can use to apply styles dynamically.


  {(provided, snapshot) => (
    <div style="{{">
      {item.content}
    </div>
  )}

In this example, the background color of the item changes to light green while it’s being dragged.

Customizing the Drag Handle

You can use the provided.dragHandleProps to create a custom drag handle. This is useful if you want to give the user a specific area to click and drag the item.


  {(provided) => (
    <div style="{{">
      <span style="{{">☰</span>
      {item.content}
    </div>
  )}

In this example, we add a three-line icon (☰) that serves as the drag handle. The cursor changes to “grab” when hovering over the handle, indicating that the item is draggable.

Styling the Drop Zone

You can also customize the appearance of the drop zone (the area where the items can be dropped). You can use the provided.droppableProps and provided.innerRef to style the drop zone.


  {(provided) => (
    <div style="{{">
      {/* Your items here */}
      {provided.placeholder}
    </div>
  )}

In this example, we added a dashed gray border and a minimum height to the drop zone.

Advanced Use Cases

react-beautiful-dnd is not limited to simple list reordering. Here are some advanced use cases:

1. Dragging Between Lists

You can implement dragging between multiple lists. This involves managing multiple states for each list and updating the state based on the source and destination droppable IDs in the onDragEnd function.

Here’s a simplified example of how you can handle dragging between two lists:

import { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

function Home() {
  const [lists, setLists] = useState({
    list1: {
      title: 'List 1',
      items: [
        { id: 'item-1', content: 'Item 1 in List 1' },
        { id: 'item-2', content: 'Item 2 in List 1' },
      ],
    },
    list2: {
      title: 'List 2',
      items: [
        { id: 'item-3', content: 'Item 1 in List 2' },
      ],
    },
  });

  const onDragEnd = (result) => {
    const { source, destination, draggableId } = result;

    if (!destination) {
      return;
    }

    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return;
    }

    const sourceListId = source.droppableId;
    const destinationListId = destination.droppableId;

    const sourceList = lists[sourceListId];
    const destinationList = lists[destinationListId];

    if (sourceListId === destinationListId) {
      // Reordering within the same list
      const reorderedItems = Array.from(sourceList.items);
      const [removed] = reorderedItems.splice(source.index, 1);
      reorderedItems.splice(destination.index, 0, removed);

      setLists({
        ...lists,
        [sourceListId]: {
          ...sourceList,
          items: reorderedItems,
        },
      });
    } else {
      // Moving between lists
      const sourceItems = Array.from(sourceList.items);
      const [removed] = sourceItems.splice(source.index, 1);
      const destinationItems = Array.from(destinationList.items);
      destinationItems.splice(destination.index, 0, removed);

      setLists({
        ...lists,
        [sourceListId]: {
          ...sourceList,
          items: sourceItems,
        },
        [destinationListId]: {
          ...destinationList,
          items: destinationItems,
        },
      });
    }
  };

  return (
    <div style="{{">
      
        {Object.entries(lists).map(([listId, list]) => (
          <div style="{{">
            <h2>{list.title}</h2>
            
              {(provided) => (
                <div style="{{">
                  {list.items.map((item, index) => (
                    
                      {(provided) => (
                        <div style="{{">
                          {item.content}
                        </div>
                      )}
                    
                  ))}
                  {provided.placeholder}
                </div>
              )}
            
          </div>
        ))}
      
    </div>
  );
}

export default Home;

This code example demonstrates the core logic for dragging items between lists. You’ll need to adapt it to your specific data structure and UI requirements.

2. Nested Drag and Drop

You can create complex layouts with nested drag-and-drop functionality. This involves nesting Droppable and Draggable components within each other. However, you need to be careful with the index values and the logic in your onDragEnd function to ensure the correct behavior.

3. Custom Drag Handles

You’ve already seen how to customize the drag handle. This is a powerful feature that lets you provide a more intuitive and user-friendly drag experience.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them when working with react-beautiful-dnd:

  • Forgetting to include provided.placeholder: The provided.placeholder element is crucial for maintaining space where the dragged item was originally located. Without it, the items below the dragged item will jump up, and the visual feedback will be incorrect. Make sure to include {provided.placeholder} inside your Droppable component.
  • Incorrectly using index: The index prop in the Draggable component is essential for determining the item’s position in the list. Ensure that you are passing the correct index based on the item’s position in your data array.
  • Not handling the onDragEnd correctly: The onDragEnd function is where the magic happens. Make sure you are updating your state correctly based on the result object. Double-check that your logic correctly reorders or moves items in your data array.
  • Not providing unique ids: Each item needs a unique id (draggableId) for the library to track them correctly. Make sure your data has unique identifiers for each item.
  • Performance issues with large lists: While the library is optimized, very large lists can still cause performance issues. Consider techniques like virtualization (only rendering the items currently in view) to improve performance.
  • Accessibility issues: Ensure that your implementation is accessible to all users. Test your drag-and-drop functionality with a screen reader and keyboard navigation to ensure that it works as expected.

Key Takeaways

  • react-beautiful-dnd simplifies adding drag-and-drop functionality to your React and Next.js applications.
  • The core components are DragDropContext, Droppable, and Draggable.
  • The onDragEnd function is crucial for updating the state after a drag operation.
  • Customization options allow you to tailor the appearance and behavior of the drag-and-drop interactions.
  • Always consider accessibility and performance when implementing drag-and-drop features.

FAQ

Q: How do I handle dragging between multiple lists?

A: You need to manage the state for each list and update the state in the onDragEnd function based on the source.droppableId and destination.droppableId to determine which list the item moved from and to.

Q: Can I customize the appearance of the dragged item?

A: Yes, you can use the provided.draggableProps.style to apply custom styles to the dragged item while it’s being dragged.

Q: How can I add a custom drag handle?

A: You can use the provided.dragHandleProps to create a custom drag handle. Wrap the element you want to use as the handle with these props.

Q: What if I have performance issues with large lists?

A: Consider using virtualization to only render the items currently in view, or explore techniques to optimize your data structure or the way you update state.

Next Steps

By using react-beautiful-dnd, you can significantly enhance the usability of your Next.js applications by providing an intuitive drag-and-drop experience. This library offers a robust and accessible solution that empowers you to create more interactive and user-friendly interfaces. Experiment with different customization options, explore advanced use cases like dragging between lists or nested drag-and-drop, and always keep accessibility and performance in mind. With a little practice, you’ll be building drag-and-drop interfaces like a pro, creating engaging and delightful experiences for your users.