React.js/Redux + Drag&Drop
Spenser Brinkman
Posted on March 27, 2021
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
...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
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);
}
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();
}
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>
);
}
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();
}
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);
}
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>
);
}
In the end, you'll end up with something that can do a little dance like this:
Posted on March 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.