Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple select, then drag and drop #3601

Open
Gerarca opened this issue Oct 19, 2023 · 3 comments
Open

Multiple select, then drag and drop #3601

Gerarca opened this issue Oct 19, 2023 · 3 comments

Comments

@Gerarca
Copy link

Gerarca commented Oct 19, 2023

Describe the bug
I have 2 list, and I just want drag and drop objects from one list to another, each item have checkbox for select elemet, so I select multiple items through the checkbox, I get this issue:

Error Objects are not valid as a React child (found: object with keys {name, checked, array}). If you meant to render a collection of children, use an array instead

so, immediately I update the function useDrag on Items.js component, but I'm getting the same issue, I have several weeks with this issue, and I don't know what's wrong? , I'm going to share the code with live reproduction:

Reproduction

Live Reproduction

Steps to reproduce the behavior:

  1. select one or several items of list left
  2. Try drag and drop on list right
  3. See error

Expected behavior
I just want Drag and Drop multiple items simultaneously

Screenshots
image list

Image issue

what's wrong? what I'm doing wrong?

Thanks!!!

@Nihal-Chandwani
Copy link

did you find any solution?

@Gerarca
Copy link
Author

Gerarca commented Mar 22, 2024

Hello!!! Yes I did, I use html 5 and javascript, let me show you.

file index.js
`
import React, { useState } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

import Cart from "./Cart";
import ItemsDragLayer from "./ItemsDragLayer";

const styles = {
main: {
width: "100%",
margin: "0 auto",
textAlign: "center"
},
content: {
display: "flex",
flexFlow: "row",
justifyContent: "left",
alignItems: "stretch",
alignContent: "stretch"
}
};

export default function List() {
const [leftItems, setLeftItems] = useState([
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8"
]);
const [rightItems, setRightItems] = useState([]);

const addItemsToCart = (items, source, dropResult) => {
const newLeftItems =
source === "left"
? leftItems.filter((x) => items.findIndex((y) => x === y) < 0)
: leftItems.concat(items);
const newRightItems =
source === "left"
? rightItems.concat(items)
: rightItems.filter((x) => items.findIndex((y) => x === y) < 0);
setLeftItems(newLeftItems);
setRightItems(newRightItems);
};
return (



Drag and drop multiple items with React DnD


Use Shift or Cmd key to multi-select









);
}

`

file cart.js
`
import React, { useState, useEffect } from "react";
import Item from "./Item";
import { useDrop } from "react-dnd";

const styles = {
content: {
borderStyle: "solid",
paddingTop: 25,
paddingBottom: 25,
marginLeft: 50,
width: 300,
height: 300
}
};

export default function Cart(props) {
const [{ isOver, canDrop }, drop] = useDrop({
accept: "ITEM",
drop: () => ({ data: props.data }),
canDrop: (item, monitor) => {
return item.source !== props.id;
},
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
});

const [selectedFields, setSelectedFields] = useState([]);
const [lastSelectedIndex, setLastSelectedIndex] = useState(-1);

const clearItemSelection = () => {
setSelectedFields([]);
setLastSelectedIndex(-1);
};

const handleItemSelection = (index, cmdKey, shiftKey) => {
let newSelectedFields;
const fields = props.fields;
const field = index < 0 ? "" : fields[index];
const newLastSelectedIndex = index;
if (!cmdKey && !shiftKey) {
newSelectedFields = [field];
} else if (shiftKey) {
if (lastSelectedIndex >= index) {
newSelectedFields = [].concat.apply(
selectedFields,
fields.slice(index, lastSelectedIndex)
);
} else {
newSelectedFields = [].concat.apply(
selectedFields,
fields.slice(lastSelectedIndex + 1, index + 1)
);
}
} else if (cmdKey) {
const foundIndex = selectedFields.findIndex((f) => f === field);
// If found remove it to unselect it.
if (foundIndex >= 0) {
newSelectedFields = [
...selectedFields.slice(0, foundIndex),
...selectedFields.slice(foundIndex + 1)
];
} else {
newSelectedFields = [...selectedFields, field];
}
}

const finalList = fields
  ? fields.filter((f) => newSelectedFields.find((a) => a === f))
  : [];

setSelectedFields(finalList);
setLastSelectedIndex(newLastSelectedIndex);

};

useEffect(() => {
// This gets called after every render, by default
// (the first one, and every one after that)
clearItemSelection();
// If you want to implement componentWillUnmount,
// return a function from here, and React will call
// it prior to unmounting.
// return () => console.log('unmounting...');
}, []);

const items = props.fields.map((field, index) => (

));
return (


{" "}
{items}{" "}

);
}
`

file item.js

`import React, { useEffect } from "react";
import { getEmptyImage } from "react-dnd-html5-backend";
import { useDrag } from "react-dnd";

const getFieldStyle = (isDragging, selected) => {
const style = {
borderStyle: "dashed",
borderWidth: 1,
height: 30,
margin: 5
};
style.backgroundColor = selected ? "pink" : "#87cefa";
style.opacity = isDragging ? 0.5 : 1;
return style;
};

export default function Item(props) {
const [{ isDragging }, drag, preview] = useDrag({
type: "ITEM" ,
item: (item, monitor) => {
let dragFields;
//If for Drag multiple Elemets
if (props.selectedFields.length > 0 ) {
dragFields = props.selectedFields;
} else { //Else for Drag one element
dragFields = [props.name];
}
return { fields: dragFields, source: props.selectedSource };
},
end: (item, monitor) => {
const dropResult = monitor.getDropResult();
// When dropped on a compatible target, do something
console.log(dropResult);
if (dropResult) {
props.addItemsToCart(item.fields, item.source, dropResult);
props.clearItemSelection();
}
},
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});

// const opacity = isDragging ? 0.4 : 1;

const handleRowSelection = (cmdKey, shiftKey, index) => {
props.handleSelection(index, cmdKey, shiftKey);
};

useEffect(() => {
// This gets called after every render, by default
// (the first one, and every one after that)

// Use empty image as a drag preview so browsers don't draw it
// and we can draw whatever we want on the custom drag layer instead.
preview(getEmptyImage(), {
  // IE fallback: specify that we'd rather screenshot the node
  // when it already knows it's being dragged so we can hide it with CSS.
  captureDraggingState: true
});
// If you want to implement componentWillUnmount,
// return a function from here, and React will call
// it prior to unmounting.
// return () => console.log('unmounting...');

}, []);

const selected = props.selectedFields.find((field) => props.name === field);
return (
<div
ref={drag}
style={getFieldStyle(false, selected)}
onClick={(e) => handleRowSelection(e.metaKey, e.shiftKey, props.index)}
>
{props.name}

);
}

`

file ItemsDragLayer.js
`
import React from "react";
import ItemsTemplate from "./ItemsTemplate";
import { useDragLayer } from "react-dnd";

const layerStyles = {
position: "fixed",
pointerEvents: "none",
zIndex: 100,
left: 0,
top: 0,
width: "100%",
height: "100%"
};

const getFieldStyle = (isDragging) => {
const style = {
width: 300,
maxWidth: 300
};
style.opacity = isDragging ? 0.8 : 1;
return style;
};

const getItemStyles = (currentOffset) => {
if (!currentOffset) {
return {
display: "none"
};
}

const { x, y } = currentOffset;

const transform = translate(${x}px, ${y}px);
return {
transform,
WebkitTransform: transform
};
};

export default function FieldDragLayer(props) {
const { itemType, isDragging, item, currentOffset } = useDragLayer(
(monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging()
})
);

const renderItem = (type, item) => {
switch (type) {
case "ITEM":
return ;
default:
return null;
}
};
if (!isDragging) {
return null;
}

return (




{renderItem(itemType, item)}



);
}
`

file ItemsTemplate.js
`
import React from "react";

const getFieldStyle = (isDragging) => {
const style = {
borderStyle: "dashed",
borderWidth: 1,
height: 30,
margin: 5,
backgroundColor: "pink"
};
style.opacity = isDragging ? 0.5 : 1;
return style;
};

const ItemsTemplate = ({ fields }) => {
const rows = fields.map((field) => (


{field}

));
return
{rows}
;
};

export default ItemsTemplate;

`

@GeorgeChen-666
Copy link

For my understanding, to make a multiple selection, you need to make value isDragging of useDrag is correct.

  const [{ isDragging }, dragRef, previewRef] = useDrag({
    item: { id: data.id, originalIndex: index },
    isDragging: (monitor) => some logic
    type: 'Card',
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

And then make Card data is correct by setState or dispatch redux actions in useDrop.
I suggest do not direct update the data in hover of useDrop when making a multiple selection.
You can add a fake card to mark the drop destination.
This can avoid the troubles caused by excessive choices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants