I am working on React and DND, I am experimenting with an example. For example, I have two columns between which I can drag-and-drop the cards back and forth that works fine, now I want the second column to contain no more than 4 items, but items can be dragged out. In the documentation I read that this was done by setting 'isDropDisabled' to true. Then I can indeed drag no more to this column, and I can leave. Now I've tried influencing that with the this.state.isUnlocked variable. So if there are more than 4 this value will be true otherwise this value will be false. Only the code does not respond to this.state.isUnlocked. Only if I type it directly in but then it is no longer variable. Is there anyone who can help me with this?
This is my code so far:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { Row, Col, Card, CardHeader, CardBody, Button } from "shards-react";
// fake data generator
const getItems = (count, offset = 0) =>
Array.from({ length: count }, (v, k) => k).map(k => ({
id: `item-${k + offset}`,
content: `item ${k + offset}`
}));
// a little function to help us with reordering the result
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
/**
* Moves an item from one list to another list.
*/
const move = (source, destination, droppableSource, droppableDestination) => {
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
return result;
};
const grid = 8;
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: 'none',
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? 'lightgreen' : 'grey',
// styles we need to apply on draggables
...draggableStyle
});
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? 'lightblue' : 'lightgrey',
padding: grid,
width: 250
});
class qSortOverview extends React.Component {
constructor(props) {
super(props);
this.state = {
items: getItems(10),
selected: getItems(2, 10),
iUnlocked: false,
};
this.canvasRef = React.createRef();
}
/**
* A semi-generic way to handle multiple lists. Matches
* the IDs of the droppable container to the names of the
* source arrays stored in the state.
*/
id2List = {
droppable: 'items',
droppable2: 'selected'
};
getList = id => this.state[this.id2List[id]];
onDragEnd = result => {
const { source, destination } = result;
// dropped outside the list
if (!destination) {
return;
}
if (source.droppableId === destination.droppableId) {
const items = reorder(
this.getList(source.droppableId),
source.index,
destination.index
);
let state = { items };
if (source.droppableId === 'droppable2') {
state = { selected: items };
}
this.setState(state);
} else {
const result = move(
this.getList(source.droppableId),
this.getList(destination.droppableId),
source,
destination
);
this.setState({
items: result.droppable,
selected: result.droppable2
});
//Linker rij
console.log(this.state.items);
if(this.state.items.length > 4){
console.log('to much items');
}
//Rechter rij
console.log(this.state.selected);
if(this.state.selected.length > 4){
this.setState({
isUnlocked: true,
})
}
}
};
// Normally you would want to split things out into separate components.
// But in this example everything is just done in one place for simplicity
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Row>
<Col lg="6" md="6" sm="6" className="mb-4">
<Droppable droppableId="droppable" >
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}>
{this.state.items.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</Col>
<Col lg="6 " md="6" sm="6" className="mb-4">
<Droppable droppableId="droppable2" isDropDisabled={this.state.isUnlocked}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}>
{this.state.selected.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</Col>
</Row>
</DragDropContext>
);
}
};
export default qSortOverview;
At the moment the right column contains 2 items, when there are 4 items it should no longer be possible to add items, just remove them.
You need change this rows:
if (this.state.selected.length > 4) {
this.setState({
isUnlocked: true
});
}
to
if (this.state.selected.length > 4) {
this.setState({
isUnlocked: true
});
} else {
this.setState({
isUnlocked: false
});
}
Related
How can I render only the icon cartIcon dynamically? Because right now, like the code below, when I enter in the component with the mouse, all the icons appears not only the icon of the single product.
I think because of map but how can I render only to it?
interface IItemsProps {
products: ProductsType;
}
const Items: React.FunctionComponent<IItemsProps> = ({ products }) => {
const [state, setState] = React.useState<boolean>(false);
const handleMouseEnter = () => {
setState(true);
};
const handleMouseLeave = () => {
setState(false);
};
const itemUI = products.map((item: SingleProductsType) => {
const { name, price, _id } = item;
return (
<WrapperSingleItem key={uuidv4()} id={_id}>
{state && <IconsCarts />} ** //HERE I NEED TO SHOW THIS COMPONENT ONLY WHEN I
// ENTER WITH THE MOUSE BUT ONLY FOR THE SELECTED
//PRODUCT NOT ALL OF THEM **
<ImgProduct
src={mouse}
alt={name}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
<WrapperTextProduct>
<TextName>{name}</TextName>
<div>
<TextActualPrice>$ {price}</TextActualPrice>
<TextPreviousPrice>
$ {Math.trunc((price * 20) / 100 + price)}.00
</TextPreviousPrice>
</div>
</WrapperTextProduct>
</WrapperSingleItem>
);
});
return <WrapperItems>{itemUI}</WrapperItems>;
};
export default Items;
You could store the hovered _id in state, so you know which one it was.
const [state, setState] = React.useState<string | null>(null); // or `number` ?
Then
{state === _id && <IconsCarts />}
<ImgProduct
src={mouse}
alt={name}
onMouseEnter={() => setState(_id)}
onMouseLeave={() => setState(null)}
/>
Or you could move the useState into a component that is called every loop of your map, so that each item has its own private state.
function MyItem({item}: { item: SingleProductsType }) {
const [state, setState] = React.useState<boolean>(false);
const { name, price, _id } = item;
return (
<WrapperSingleItem key={uuidv4()} id={_id}>
{state && <IconsCarts />}
<ImgProduct
src={mouse}
alt={name}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
<WrapperTextProduct>
<TextName>{name}</TextName>
<div>
<TextActualPrice>$ {price}</TextActualPrice>
<TextPreviousPrice>
$ {Math.trunc((price * 20) / 100 + price)}.00
</TextPreviousPrice>
</div>
</WrapperTextProduct>
</WrapperSingleItem>
);
}
Now you can do:
{products.map((item: SingleProductsType) => <MyItem item={item} />}
Lastly, if all you want to do is show/hide the cart icon when you enter some element with the mouse, this solution is probably way overkill. You can do this with CSS alone, which is going to be a far cleaner solution since it takes no javascript code whatsoever, and you don't have to track state at all.
.item {
width: 100px;
height: 100px;
background: #aaa;
margin: 10px;
}
.item button {
display: none;
}
.item:hover button {
display: block;
}
<div class="item">
Foo
<button>Add to cart</button>
</div>
<div class="item">
Bar
<button>Add to cart</button>
</div>
<div class="item">
Baz
<button>Add to cart</button>
</div>
With a boolean in state, all you know is whether to show an icon, but what about knowing which list item to show the icon on? Instead of state being a boolean, how about we use the index of the product.
interface IItemsProps {
products: ProductsType;
}
const Items: React.FunctionComponent<IItemsProps> = ({ products }) => {
const [state, setState] = React.useState<number>(-1);
const handleMouseEnter = (index) => {
setState(index);
};
const handleMouseLeave = () => {
setState(-1);
};
const itemUI = products.map((item: SingleProductsType, index: number) => {
const { name, price, _id } = item;
return (
<WrapperSingleItem key={uuidv4()} id={_id}>
{state === index && <IconsCarts />} ** //Check if index matches state before showing icon **
<ImgProduct
src={mouse}
alt={name}
onMouseEnter={() => handleMouseEnter(index)}
onMouseLeave={handleMouseLeave}
/>
<WrapperTextProduct>
<TextName>{name}</TextName>
<div>
<TextActualPrice>$ {price}</TextActualPrice>
<TextPreviousPrice>
$ {Math.trunc((price * 20) / 100 + price)}.00
</TextPreviousPrice>
</div>
</WrapperTextProduct>
</WrapperSingleItem>
);
});
return <WrapperItems>{itemUI}</WrapperItems>;
};
export default Items;
Now the condition to show the icon is if the index of the list item matches the index in state. And we pass in the index to handleMouseEnter to set state to that index, and handleMouseLeave will reset it back to -1.
In my project I am trying to change my information into react-dnd beutiful drag and drop.
But as i changed it, the drag and drop does not work for new items inserted into the array.
How do I make it so that it works still in this example?
code sandbox:
https://codesandbox.io/s/react-drag-and-drop-react-beautiful-dnd-forked-oo8nd?file=/src/index.js:0-5732
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
// fake data generator
const getItems = (count, offset = 0) =>
Array.from({ length: count }, (v, k) => k).map((k) => ({
id: `item-${k + offset}-${new Date().getTime()}`,
content: `item ${k + offset}`
}));
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
/**
* Moves an item from one list to another list.
*/
const move = (source, destination, droppableSource, droppableDestination) => {
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
return result;
};
const grid = 8;
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? "lightgreen" : "grey",
// styles we need to apply on draggables
...draggableStyle
});
const getListStyle = (isDraggingOver) => ({
background: isDraggingOver ? "lightblue" : "lightgrey",
padding: grid,
width: 250
});
function QuoteApp() {
const [state, setState] = useState([getItems(10), getItems(5, 10)]);
function onDragEnd(result) {
const { source, destination } = result;
// dropped outside the list
if (!destination) {
return;
}
const sInd = +source.droppableId;
const dInd = +destination.droppableId;
if (sInd === dInd) {
const items = reorder(state[sInd], source.index, destination.index);
const newState = [...state];
newState[sInd] = items;
setState(newState);
} else {
const result = move(state[sInd], state[dInd], source, destination);
const newState = [...state];
newState[sInd] = result[sInd];
newState[dInd] = result[dInd];
setState(newState.filter((group) => group.length));
}
}
return (
<div>
<button
type="button"
onClick={() => {
setState([...state, []]);
}}
>
Add new group
</button>
<button
type="button"
onClick={() => {
setState([...state, getItems(1)]);
}}
>
Add new item
</button>
<div style={{ display: "flex" }}>
<DragDropContext onDragEnd={onDragEnd}>
{state.map((el, ind) => (
<Droppable key={ind} droppableId={`${ind}`}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
{...provided.droppableProps}
>
{el.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<div
style={{
display: "flex",
justifyContent: "space-around"
}}
>
ind:{ind} , index{index} ,{item.content}
<button
type="button"
onClick={() => {
const newState = [...state];
newState[ind].splice(index, 1);
setState(
newState.filter((group) => group.length)
);
}}
>
delete
</button>
<button
type="button"
onClick={() => {
const newState = [...state];
newState[ind].splice(
index,
0,
getItems(1, index)
);
setState(
newState.filter((group) => group.length)
);
}}
>
add s/o
</button>
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
))}
</DragDropContext>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<QuoteApp />, rootElement);
The specific change is the "add s/o" button
Thankyou for your time
Issue
When you insert/add a "s/o" (sorry, not sure what that is) you splice in the return value from getItems which is an array, but your main state shape is an array of objects.
const getItems = (count, offset = 0) =>
Array.from({ length: count }, (v, k) => k).map((k) => ({
id: `item-${k + offset}-${new Date().getTime()}`,
content: `item ${k + offset}`
}));
...
<button
type="button"
onClick={() => {
const newState = [...state];
newState[ind].splice(
index,
0,
getItems(1, index) // <-- inserts array!!
);
setState(
newState.filter((group) => group.length)
);
}}
>
add s/o
</button>
You've inserted an array instead of the object.
{newState: Array(2)}
newState: Array(2)
0: Array(11)
0: Array(1) // <-- inserted array
0: Object // <-- object you want inserted
id: "item-0-1609711045911"
content: "item 0"
1: Object
id: "item-0-1609711042837"
content: "item 0"
2: Object
3: Object
4: Object
5: Object
6: Object
7: Object
8: Object
9: Object
10: Object
1: Array(5)
Solution
Seems a rather simple solution is to pop the single inserted object from getItems when splicing in the new "s/o".
<button
type="button"
onClick={() => {
const newState = [...state];
newState[ind].splice(
index,
0,
getItems(1, index).pop(), // <-- pop value from array
);
setState(
newState.filter((group) => group.length)
);
}}
>
add s/o
</button>
I've successfully implemented Beautiful DnD in a React app where the draggable items are Material UI Expansion Panels. I have the list of items resorting onDragEnd and saving the new sorted list in state.
I'm using React hooks, useState, Material-UI Expansion Panel, and React-Beautiful-DnD.
When this part of the app loads, the first expansion panel is expanded and all others are collapsed.
What I've been trying to get working is how to close the expansion panel [onDragStart | onBeforeDragStart | onDragUpdate] and then open the expansion panel onDragEnd.
I have stored the state of each expansion panel along with other info in an array that loops over and renders each expansion panel:
{
name: string,
expanded: string,
...
}
I'm thinking this is an issue where the state is not getting updated to where the expansion panel is not picking up the change.
I've tried using the snapshot.isDragging on the item to change the expansion panels expanded state and even tried targeting the specific expansion panel, finding the corresponding item in the state list and updating the expanded prop onDragStart, onBeforeDragStart, and onDragUpdate. None of these have worked so far.
Here's part of the component handling the DnD
const newArray = ReorderList(srcList, ixSelectedItem, 0);
const panelState: Array<ExpansionState> = [];
// eslint-disable-next-line array-callback-return
newArray.map(item => {
panelState.push({
name: item as string,
expanded: item === selectedItem,
isDragging: false
});
});
const [itemListWithState, setItemListWithState] = useState(panelState);
const handleOnDragEnd = (result: any): void => {
const { destination, draggableId, source } = result;
if (!destination) {
return;
}
if (destination.droppableId === source.droppableId && destination.index === source.index) {
return;
}
const newList = ReorderList(itemListWithState, source.index, destination.index) as Array<ExpansionState>;
const ix = newList.findIndex(item => item.name === draggableId);
newList[ix].isDragging = false;
newList[ix].expanded = !newList[ix].expanded;
setItemListWithState(newList);
};
const handleOnDragUpdate = (result: any): void => {
const { draggableId } = result;
const ix = itemListWithState.findIndex(item => item.name === draggableId);
if (!itemListWithState[ix].isDragging) {
itemListWithState[ix].expanded = !itemListWithState[ix].expanded;
itemListWithState[ix].isDragging = true;
}
};
...
...
return (
<DragDropContext onDragEnd={handleOnDragEnd} onDragUpdate={handleOnDragUpdate}>
<Droppable droppableId="list">
{(provided, snapshot) => {
return (
<div ref={provided.innerRef} {...provided.droppableProps}>
{itemListWithState.map((item: ExpansionState, index: number) => {
return (
<Draggable key={item.name} draggableId={item.name} index={index}>
{(provided, snapshot) => {
return (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<GraphContainer id={item.name} isGraphExpanded={item.expanded} {...props}></GraphContainer>
</div>
);
}}
</Draggable>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</DragDropContext>
);
Here's the component housing the expansion panel:
return (
<div style={{ margin: '20px 0' }}>
<Panel
expanded={props.isGraphExpanded}
summaryTitle={getResourceString(props.id, Resources.Performance)}
summaryDetail={props.graph}
isDraggable={true}
subPanel={
<Panel
expanded={false}
summaryTitle={getResourceString(props.id, Resources.DataTable)}
summaryDetail={props.table}
isDraggable={false}
></Panel>
}
></Panel>
</div>
);
I want to implement drag and drop of elements with sorting and I don't want to use any external library.
There will be 2 containers, I want to sort the elements within a container and drag/drop between the two containers. Any suggestions?
I've tried HTML5 and other things but didn't worked
In the above screen shot, I want to drag and drop the elements from shown to hidden and vice-versa, also sorting the elements among "Shown" row
/**
* Displays Dialog for Table show/hide columns
* #default {string} submitLabel, cancelLabel
* #default {bool} open
* #default {array} suggestions
*/
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import SettingsIcon from "#material-ui/icons/Settings";
import IconButton from "#material-ui/core/IconButton";
import { theme } from "../../../../utilities/theme-default";
import HLTooltip from "../../../atoms/ToolTip";
import HLDialog from "../../../grouped/Dialog";
import HLSaveSettings from "../SaveTableSettings";
import HLTypography from "../../../atoms/Typography";
import HLChip from "#material-ui/core/Chip";
import * as colors from "../../../../utilities/color";
import Draggable from "react-draggable";
export const styles = () => ({
tableAction: {
width: "100%",
marginTop: `${theme.spacing.unit}px`
},
container: {
display: "flex"
},
title: {
flex: "0 0 auto"
},
section: {
border: `1px solid ${colors.neutralDark["120"]}`,
marginTop: `${theme.spacing.unit * 3}px`,
padding: `${theme.spacing.unit}px 0 ${theme.spacing.unit * 3}px ${theme
.spacing.unit * 6}px`,
display: "flex"
},
chipTextStyle: {
marginTop: `${theme.spacing.unit * 4}px`
},
chipStyle: {
padding: `${theme.spacing.unit * 3}px`,
marginTop: `${theme.spacing.unit}px`
},
dialogStyle: {
width: "500px"
},
defaultSection: {
marginTop: `${theme.spacing.unit * 2}px`
}
});
/**
* state component which handles table show/hide columns
*/
export class HLShowHide extends React.Component {
state = {
open: false,
headers: this.props.headers
};
/**
* function to display dialog for show/hide opetations
*/
dialogToggle = () => {
this.setState({
open: !this.state.open
});
};
onStart() {
this.setState({ activeDrags: this.state.activeDrags + 1 });
}
onStop() {
this.setState({ activeDrags: this.state.activeDrags - 1 });
}
onDragStart = (e, sectionIndex, index) => {
e.stopPropagation();
this.draggedItem = this.state.headers.sections[sectionIndex].items[index];
this.dragItemSection = sectionIndex;
this.dragItemIndex = index;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/html", e.target.parentNode);
e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
};
onDragOver = (sectionIndex, index) => {
const draggedOverItem = this.state.headers.sections[sectionIndex].items[
index
];
// if the item is dragged over itself, ignore
if (this.draggedItem === draggedOverItem) {
return;
}
if (this.dragItemSection !== sectionIndex) {
var otherItems = this.state.headers.sections[this.dragItemSection].items;
otherItems.splice(this.dragItemIndex, 1);
}
// filter out the currently dragged item
let items = this.state.headers.sections[sectionIndex].items.filter(
item => item !== this.draggedItem
);
// add the dragged item after the dragged over item
items.splice(index, 0, this.draggedItem);
const sections = this.state.headers.sections;
sections[sectionIndex].items = items;
sections[this.dragItemSection].items = otherItems;
this.setState({ headers: { ...this.state.headers, sections } });
};
onDragOverSection = sectionIndex => {
if (this.state.headers.sections[sectionIndex].length === 0) {
var otherItems = this.state.headers.sections[this.dragItemSection].items;
otherItems.splice(this.dragItemIndex, 1);
let items = this.state.headers.sections[sectionIndex].items;
items.push(this.draggedItem);
const sections = this.state.headers.sections;
sections[this.dragItemSection].items = otherItems;
sections[sectionIndex].items = items;
this.setState({ headers: { ...this.state.headers, sections } });
}
};
onDragEnd = () => {
this.draggedIdx = null;
};
render() {
const { classes, submitLabel, cancelLabel } = this.props;
const { open, headers } = this.state;
const dragHandlers = {
onStart: this.onStart.bind(this),
onStop: this.onStop.bind(this)
};
const dialogBody = (
<div className={classes.dialogStyle}>
{headers.sections.map((section, sectionIndex) => (
<div className={classes.section}>
<span className={classes.chipTextStyle}>
<HLTypography
variant={
theme.palette.tableConstant.showHideColumn.sections.variant
}
color={
theme.palette.tableConstant.showHideColumn.sections.color
}
>
{section.label}
</HLTypography>
</span>
<span onDragOver={() => this.onDragOverSection(sectionIndex)}>
{section.items.map((item, itemIndex) => (
<div
className={classes.chipStyle}
key={item.value}
data-id={`${itemIndex}_${sectionIndex}`}
onDragOver={() => this.onDragOver(sectionIndex, itemIndex)}
>
<Draggable
onDrag={this.handleDrag.bind(this)}
{...dragHandlers}
>
<HLChip label={item.label} />
</Draggable>
</div>
))}
</span>
</div>
))}
<div className={classes.defaultSection}>
<HLSaveSettings
defaultLabel={headers.defaultLabel}
isDefault={headers.isDefault}
/>
</div>
</div>
);
return (
<React.Fragment>
{open && (
<div className={classes.container}>
<div className={classes.tableAction}>
<HLDialog
open={open}
submitLabel={submitLabel}
cancelLabel={cancelLabel}
bodyText={dialogBody}
maxWidth="xl"
onSubmit={() => {}}
onCancel={() => {
this.dialogToggle();
}}
/>
</div>
</div>
)}
{!open && (
<React.Fragment>
<HLTooltip title="Settings" placement="top">
<IconButton aria-label="Settings">
<SettingsIcon onClick={this.dialogToggle} />
</IconButton>
</HLTooltip>
</React.Fragment>
)}
</React.Fragment>
);
}
}
HLShowHide.propTypes = {
classes: PropTypes.object,
headers: PropTypes.arrayOf().isRequired,
open: PropTypes.bool,
submitLabel: PropTypes.string,
cancelLabel: PropTypes.string
};
HLShowHide.defaultProps = {
open: false,
submitLabel: "Set",
cancelLabel: "Cancel"
};
export default withStyles(styles)(HLShowHide);
my code which I tried
You can assign onDrop and onDrag events to div. You can hold each component in an array and when you drag you can remove the item from and array and when you drop, you can add this item to array. After that you can sort the array by array.sort()
<div onDrag={this.handleDrag()} onDrop={thishandleDrop()}>
handleDrag = () => {
//remove your item in array here
}
handleDrag = () => {
//add your item to array here
}
I have react-beautiful-dnd horizontal multiple list(6 rows with the same items in each row) with the same items in each list.
I want to delete individual selected items from each list, but just having a button component with onClick fires the onClick while rendering the lists itself. How do i configure the list so that an individual item is deleted from that list when i click on the close/delete (x) button?
Below, is my code,
import React, { Component } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import {Button, Icon} from 'semantic-ui-react'
// a little function to help us with reordering the result
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const grid = 12;
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: 'none',
padding: grid / 2,
margin: `0 ${grid}px 0 0`,
// change background colour if dragging
background: isDragging ? 'lightgreen' : 'lightgray',
// styles we need to apply on draggables
...draggableStyle,
});
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? 'lightblue' : 'white',
display: 'flex',
padding: grid,
overflow: 'auto',
});
class DragAndDrop extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.uniqueEntries
};
this.onDragEnd = this.onDragEnd.bind(this)
this.removeSubject = this.removeSubject.bind(this)
}
onDragEnd(result) {
// dropped outside the list
if (!result.destination) {
return;
}
const items = reorder(
this.state.items,
result.source.index,
result.destination.index
);
this.setState({
items,
});
}
componentWillReceiveProps(newProps){
this.setState({
items : newProps.uniqueEntries
})
}
removeItem = (index) => {
this.state.items.splice(index, 1)
}
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Droppable droppableId="droppable" direction="horizontal">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
{...provided.droppableProps}
>
{this.state.items.map((item, index) => (
<Draggable key={item.Id} draggableId={item.Id}
index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<Button icon size='mini'
style={{backgroundColor : 'lightgray' ,padding:
'0', float: 'right'}}
onClick = {this.removeItem(index)}>
<Icon name='close' />
</Button>
{item.name}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
}
export default DragAndDrop
A Note for this bug that might hit you later and future viewers. 😋
You are not supposed to mutate the state directly. It will have some side effects or none at all.
I also recommend you generate unique IDs because if you create more list, it will also have some unexpected result even react-beautiful-dnd won't notice.
If you want to update or remove state data, use setState(). First modify existing data with a copy. Then assign the new value.
removeItem(index) {
// do magic here to filter out the unwanted element
// Update via 'setState'
this.setState({
items : newModifiedItems
})
}
For an example, my trial method below:
removeItem(e) {
e.preventDefault();
// give your single list a className so you can select them all
const sourceList = document.querySelectorAll(".list-name");
const arrList= Array.from(sourceList);
// make shallow copy of your state data
const newItems = Array.from(this.state.items);
// Find index element from whole list Arr by traversing the DOM
const removeItemIndex = arrList.indexOf(e.target.parentElement);
// Remove it
newItems.splice(removeItemIndex, 1);
this.setState({
items: newItems
})
}
I found out why it was firing at render, instead of passing the function i was initiating it so through the loop it was getting called. Then i did this and it worked. May be this will help someone who might face a similar issue.
<Button icon size='mini'
style={{backgroundColor : 'lightgray' ,padding: '0',
float: 'right', marginLeft:'15px'}}
onClick = {() => this.removeItem(index)}>
<Icon name='close' />
</Button>