Skip to content

How to Rearrange Gallery Images Using React DnD

React drag and drop (React DnD) is a beautiful and accessible drag and drop library made for React apps that let’s build interactive experiences. It supports touch interactions on mobile devices and HTML5, but it depends on touch support. One popular app that uses this drag and drop interaction is Trello.

In this post, we will learn how to build a page filled with images that allow anyone to move an image around to another position within the app in response to the drag and drop events.

The complete project is on CodeSandbox.


## Prerequisites

To complete the exercise in this post, we need the following:

- Basic knowledge of JavaScript and React.
- Node.js installed on our computer.


## Getting Started

React is a JavaScript frontend library for generating and building user interfaces for web applications.

To scaffold a new project, run the following command in our terminal:


```sh
npx create-react-app <project-name>

After the installation, navigate to the created directory and run the command below to start the application:

cd <project-name> # navigate to the directory 

npm run start # run the development server
Code language: PHP (php)

React.js starts the development environment on http://localhost:3000.

With the whole setup done, run either of the commands below to install the react-dnd ****and ****react-dnd-html5-backend libraries to our project:

yarn add react-dnd react-dnd-html5-backend
    
# or 
    
npm install react-dnd react-dnd-html5-backend
Code language: PHP (php)

react-dnd-html5-backend: This library will allow the use of the drag and drop API with react``-``dnd.

Now, let’s implement the drag and drop functionality in the project’s entry point , index.js , with the provider component.

// src/index.js
// other imports
    
// add this
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import App from "./App";
import "./styles.css";
    
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <DndProvider backend={HTML5Backend}>
      <App />
    </DndProvider>
  </StrictMode>
);
Code language: JavaScript (javascript)

In the code block above, we wrap the highest order component, App, with a provider that comes with the React DnD library. The provider serves to alert the library that the component inside the provider will have access to the library’s functionality. Also, we pass in the backend property, which is the primary backend support for the touch support.

To have access to the image on the page, we will start with creating a list of images in our app that will have the ability to drag and drop the desired image.

We won’t be diving deep into building the gallery image app from scratch for this post. However, we will focus our attention more on the functionality of using the React DnD library to rearrange the image.

Now, create an array of objects that contains the data.

# src/data.js
export default [
  {
    id: 1,
    title: 'Travel',
    img:
      'https://media.istockphoto.com/photos/beautiful-view-of-amazing-sandstone-formations-in-famous-lower-near-picture-id807387518?k=20&m=807387518&s=612x612&w=0&h=G8O867Id71Sk6LCiAnAYp-dIu9uf4YqlfnO3uj-SQ00='
   },
...
Code language: PHP (php)

As pointed out earlier, we won’t be focusing much on the styling of the app. Create a new file in the src folder called styles.css and paste the following code from this gist.

Next, let’s loop through the resulting data objects in the App.js file to see the gallery images from the Card component by passing props. Add the following code:

// src/App.js
import React from "react";
import galleryList from "./data.js";
    
const Card = ({src, title, id, index}) => {
  return (
    <div className="card">
      <img src={src} alt={title} />
    </div>
  );
};
const App = () => {
  const [images, setImages] = React.useState(galleryList);
      
return (
  <main>
    {React.Children.toArray(
      images.map((image, index) => (
        <Card
          src={image.img}
          title={image.title}
          id={image.id}
          index={index}
        />
      ))
    )}
  </main>
);
};
export default App;
Code language: JavaScript (javascript)

The result from the above should look like this:

With the library present in our app, we import the useDrag hook from React DnD.

// src/App.js
// react import
import { useDrag} from "react-dnd";
// image data objects import
    
const Card = ({src, title, id, index}) => {
const ref = React.useRef(null); 
  return (
    <div className="card">
        // card images
    </div>
  );
};

const App = () => {
      // state 
const [{ isDragging }, drag] = useDrag({
    type: "image",
    item: () => {
      return { id, index };
    },
    collect: (monitor) => {
      return {
        isDragging: monitor.isDragging()
      };
    }
});
    
return (
  <main>
    // loop through the images in Card component
  </main>
);
};
export default App;
Code language: JavaScript (javascript)

In the above code block, it does the following:

  • useRef: This hook returns a mutable ref object when initialized to the passed argument with a .current property.
  • useDrag: The hook is used on every element to make it draggable. Using the useDrag hook comes with a set of properties.
  • isDragging: A boolean variable that determines if we are currently dragging an image or not. It returns true if we are dragging the image and false if otherwise.
  • drag: Used to reference the element we want to make draggable.
  • type: Every element in React DnD requires an identifier which is a string.
  • item: It describes the item (id and index) property that match the drop targets about the drag source.
  • collect: The property contains a callback function that receives the isDragging props and monitor parameter.
  • monitor: It allows you update the props of your components in response to the drag and drop state changes.

Let’s import the useDrop component from the React DnD library.

// src/App.js
import { useDrag, useDrop } from "react-dnd";
Code language: JavaScript (javascript)

useDrop: This hook acts as a drop target in our component into the DnD system.

Next, update and add the following code in the App.js file:

// React import
import { useDrag, useDrop } from "react-dnd";
    
// data image import
    
const Card = ({ src, title, id, index }) => {
const ref = React.useRef(null);
    
const [, drop] = useDrop({
  accept: "image",
  hover: (item, monitor) => {
    if (!ref.current) {
      return;
    }
const dragIndex = item.index;
const hoverIndex = index;

if (dragIndex === hoverIndex) {
            return;
}
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;

if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
     return;
}

if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
    return;
}

moveImage(dragIndex, hoverIndex);

item.index = hoverIndex;

}
});
      
// useDrag component
    
const opacity = isDragging ? 0 : 1;
drag(drop(ref));

return (
    <div ref={ref} style={{ opacity }} className="card">
      <img src={src} alt={title} />
    </div>
);
}
    
// App component
Code language: JavaScript (javascript)

The above code does the following:

  • drop: It is called when a compatible item on the app is dropped on the target.
  • hover(item, monitor): The hover function helps to move things around.
  • !ref.current: Check whether the current attribute doesn’t exist at the defined variable ref from the useRef hook. We stop the function by passing the return keyword.
  • Next is to check whether the dragIndex and hoverIndex are equal, meaning the item moved or dragged should do nothing if the item is not moved up and down the list of items with the return keyword stopping the function from firing.
  • hoverBoundingRect: It determines the rectangle on screen on the current attribute.
  • hoverMiddleY: Get the vertical middle.
  • clientOffset: This variable determines the mouse position.
  • hoverClientY: It returns the pixels to the top.
  • Next, we check the dragIndex, hoverIndex, hoverClientY, and hoverMiddleY are equal; otherwise, stop the funtion.

For the tutorial to be complete, we need to be able to move the image from one location to another. Add the following code to the App component.

// import
    
// Card component
    
const App = () => {
  // state
      
// add this
const moveImage = React.useCallback((dragIndex, hoverIndex) => {
    setImages((prevCards) => {
    const clonedCards = [...prevCards];
    const removedItem = clonedCards.splice(dragIndex, 1)[0];
    clonedCards.splice(hoverIndex, 0, removedItem);
    return clonedCards;
});
}, []);
    
return (
  <main>
    {React.Children.toArray(
      images.map((image, index) => (
        <Card
          src={image.img}
          title={image.title}
          id={image.id}
          index={index}
          moveImage={moveImage} // add this
        />
      ))
    )}
  </main>
);
};
export default App;
Code language: JavaScript (javascript)

The code above does the following:

  • We updated the Card component with the moveImage props and called it in its component.
  • Using the useCallback hook, we rerender the updated cards by updating them with setImages
  • The splice method on the moveImage variable replaces the previous image and adds the new dragged image in its position.

Below is the complete code for the App.js file.

import React from "react";
import { useDrag, useDrop } from "react-dnd";
import galleryList from "./data.js";
const Card = ({ src, title, id, index, moveImage }) => {
  const ref = React.useRef(null);
  const [, drop] = useDrop({
    accept: "image",
    hover: (item, monitor) => {
      if (!ref.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;
      if (dragIndex === hoverIndex) {
        return;
      }
      const hoverBoundingRect = ref.current?.getBoundingClientRect();
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
      const clientOffset = monitor.getClientOffset();
      const hoverClientY = clientOffset.y - hoverBoundingRect.top;
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }
      moveImage(dragIndex, hoverIndex);
      item.index = hoverIndex;
    }
  });
  const [{ isDragging }, drag] = useDrag({
    type: "image",
    item: () => {
      return { id, index };
    },
    collect: (monitor) => {
      return {
        isDragging: monitor.isDragging()
      };
    }
  });
  const opacity = isDragging ? 0 : 1;
  drag(drop(ref));
  return (
    <div ref={ref} style={{ opacity }} className="card">
      <img src={src} alt={title} />
    </div>
  );
};
const App = () => {
  const [images, setImages] = React.useState(galleryList);
  const moveImage = React.useCallback((dragIndex, hoverIndex) => {
    setImages((prevCards) => {
      const clonedCards = [...prevCards];
      const removedItem = clonedCards.splice(dragIndex, 1)[0];
      clonedCards.splice(hoverIndex, 0, removedItem);
      return clonedCards;
    });
  }, []);
  return (
    <main>
      {React.Children.toArray(
        images.map((image, index) => (
          <Card
            src={image.img}
            title={image.title}
            id={image.id}
            index={index}
            moveImage={moveImage}
          />
        ))
      )}
    </main>
  );
};
export default App;
Code language: JavaScript (javascript)

This post discussed how to rearrange gallery images with the drag and drop feature of React DnD.

React Dnd

Back to top

Featured Post