DEV Community

Spenser Brinkman
Spenser Brinkman

Posted on • Edited on

React.js/Redux + Drag&Drop

In my most recent project, 'GreenHouse', users manage spaces that may contain any number of house plants. One of my goals with this project was to incorporate as much interactive functionality as I could to provide a fluid user experience. Drag-and-drop was a big step towards this objective. I wanted to be able to drag plants between spaces and update the containing room components appropriately, and accomplishing this was relatively straight-forward to incorporate with my Redux build.

We start off with two basic components: a PlantCard and a SpaceCard. I'm more familiar with class components, but this functionality might also be achievable using functional components.

First, our PlantCard

## PlantCard.js

import React, { Component } from 'react';

class PlantCard extends Component {

  # unrelated PlantCard functionality goes up here

  render() {
    return(
      <ul className='plant-card'>
        # plant information goes here
      </ul>
    );
  }
}

export default PlantCard
Enter fullscreen mode Exit fullscreen mode

...then our SpaceCard

## SpaceCard.js

import React, { Component } from 'react';

class SpaceCard extends Component {

  # unrelated SpaceCard functionality goes up here

  render() {
    return(
      <div className='space-card'>
        <div className='space-info'>
          # space info goes here
        </div>
        <div classname='space-plants'>
          {this.props.plants.map(plant => <PlantCard plant={plant} />)}
        </div>
      </div>
    );
  }
}

export default SpaceCard
Enter fullscreen mode Exit fullscreen mode

With our basic components set up, we can build in our drag & drop functionality. First, we'll tell our PlantCard about being dragged around.

dragStart = event = {
  const plant = JSON.stringify(this.props.plant);
  event.dataTransfer.setData('plant', plant);
}
Enter fullscreen mode Exit fullscreen mode

When the mouse button is held and dragged away from a Plant component, the component's plant prop is stored in the DataTransfer object under the keyword plant.

As a safeguard to prevent strange graphical issues, we can also add this function to our PlantCard class component:

dragOver = event => {
  event.stopPropagation();
}
Enter fullscreen mode Exit fullscreen mode

Finally, we'll attach these functions to the HTML elements being rendered by the component, and also assign 'true' to the 'draggable' attribute.

render () {
  return(
    <ul
      className='plant-card'
      onDragStart={this.dragStart}
      onDragOver={this.dragOver}
      draggable='true'
    >
      # Plant information goes here
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Toy Story

As for our SpaceCard's ability to receive dropped PlantCards, we'll follow a similar pattern as before.
First thing to note is that D&D will not work if the receiving element doesn't have an 'on drag' function, so we'll define that with a generic event.preventDefault() as a safeguard for unintended behavior.

dragOver = event => {
  event.preventDefault();
}
Enter fullscreen mode Exit fullscreen mode

Next is the powerhouse of the operation, where we actually tell the application to change the plant's associated space to the one it's being dropped upon.

drop = event => {
  event.preventDefault();
  const plant = JSON.parse(event.dataTransfer.getData('plant'));
  plant.spaceId = this.props.space.id;
  this.props.editPlant(plant);
}
Enter fullscreen mode Exit fullscreen mode

There's a lot going up there, so we'll break it down line-by-line. We start with a basic preventDefault() again as a catch all (I wish) for unwanted problems. Next, we access the plant data we have saved away by asking for that 'plant' keyword from the DataTransfer object, setting it to a variable. We take that variable, change the necessary attributes (spaceId in this case), and then pass it to a dispatch function provided by our redux store. For the sake of brevity, I've omitted the process of connecting components to the store.

Finally, we can tell the HTML all about it with a few more changes:

render() {
  return(
    <div
      className='space-card'
      onDrop={this.drop}
      onDragOver={this.dragOver}
    >
      <div className='space-info'>
        # space info goes here
      </div>
      <div classname='space-plants'>
        {this.props.plants.map(plant => <PlantCard plant={plant} />)}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the end, you'll end up with something that can do a little dance like this:
Example GIF of drag and drop

Top comments (1)

Collapse
 
revskill10 profile image
Truong Hoang Dung

Could you please put this in a CodeSandbox ?