I am working on a form builder. The basic implementation works but every time when I move elements from the tool box to the form the current state resets to initial then add newly dropped element so previously dragged element is missed from the form. Below is the linked to the source code at sandbox.
https://codesandbox.io/s/nifty-https-qx8lko
index.js
import Example from "./Example.jsx";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import "./style.sass";
function App() {
return (
<div className="App">
<DndProvider backend={HTML5Backend}>
<Example />
</DndProvider>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Example.jsx
import React, { useState } from "react";
import Form from "./Form";
import Toolbar from "./Toolbar";
const Example = (props) => {
const [Items, setItems] = useState([{ name: "Test" }]);
const addItem = (item) => {
setItems([...Items, item]);
};
return (
<div className="card" style={{ border: "1px dashed rgb(219 163 163)" }}>
<div className="card-header d-flex justify-content-between">
<h5 className="cart-title m-0">View</h5>
</div>
<div className="card-body">
<div className="row">
<div className="col-9">
<Form items={Items} addItems={addItem} />
</div>
<div className="col-3">
<Toolbar />
</div>
</div>
</div>
</div>
);
};
export default Example;
Form.jsx
import React from "react";
import { useDrop } from "react-dnd";
import { ItemTypes } from "./ItemTypes";
function Form(props) {
const addItem = (item) => {
props.addItems(item);
};
const [{ canDrop, isOver }, drop] = useDrop(() => ({
accept: ItemTypes.CARD,
drop: (item, monitor) => addItem(item),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
}));
return (
<div className="card card-default shadow-sm mt-3">
<div className="card-body">
{props.items.map((item, index) => {
return (
<div key={index} className="form-item">
{item.name}
</div>
);
})}
<div ref={drop}>
{/* {canDrop ? "Release to drop" : "Drag a box here"} */}
{isOver && canDrop && (
<div className="form-place-holder">
<div>Release to Drop</div>
</div>
)}
{!isOver && (
<div className="form-place-holder">
<div>Dropzone</div>
</div>
)}
</div>
</div>
</div>
);
}
export default Form;
Toolbar.jsx
import React from "react";
import ToolbarItem from "./ToolbarItem";
const Toolbar = () => {
const _defaultItems = () => {
return [
{
key: "Header",
name: "Header Text",
icon: "fas fa-heading",
static: true,
content: "Place holder text"
},
{
key: "Label",
name: "Label",
static: true,
icon: "fas fa-font",
content: "Place holder text"
},
{
key: "Paragraph",
name: "Paragraph",
static: true,
icon: "fas fa-paragraph",
content: "Place holder text"
},
{
key: "LineBreak",
name: "Line break",
static: true,
icon: "fas fa-arrows-alt-h"
},
{
key: "Dropdown",
canHaveAnswer: true,
name: "Dropdown",
icon: "far fa-caret-square-down",
label: "Place holder label",
field_name: "dropdown_",
options: []
},
{
key: "Tags",
canHaveAnswer: true,
name: "Tags",
icon: "fas fa-tags",
label: "Place holder label",
field_name: "tags_",
options: []
},
{
key: "Checkboxes",
canHaveAnswer: true,
name: "Checkboxes",
icon: "far fa-check-square",
label: "Place holder label",
field_name: "checkboxes_",
options: []
},
{
key: "RadioButtons",
canHaveAnswer: true,
name: "Multiple choices",
icon: "far fa-dot-circle",
label: "Place holder label",
field_name: "radiobuttons_",
options: []
},
{
key: "TextInput",
canHaveAnswer: true,
name: "Text input",
label: "Place holder label",
icon: "fas fa-font",
field_name: "text_input_"
},
{
key: "NumberInput",
canHaveAnswer: true,
name: "Number input",
label: "Place holder label",
icon: "fas fa-plus",
field_name: "number_input_"
},
{
key: "TextArea",
canHaveAnswer: true,
name: "Multi line input",
label: "Place holder label",
icon: "fas fa-text-height",
field_name: "text_area_"
},
{
key: "TwoColumnRow",
canHaveAnswer: false,
name: "Two columns row",
label: "",
icon: "fas fa-columns",
field_name: "two_col_row_"
},
{
key: "ThreeColumnRow",
canHaveAnswer: false,
name: "Three columns row",
label: "",
icon: "fas fa-columns",
field_name: "three_col_row_"
},
{
key: "FourColumnRow",
canHaveAnswer: false,
name: "Four columns row",
label: "",
icon: "fas fa-columns",
field_name: "four_col_row_"
},
{
key: "Image",
name: "Image",
label: "",
icon: "far fa-image",
field_name: "image_",
src: ""
},
{
key: "Rating",
canHaveAnswer: true,
name: "Rating",
label: "Place holder label",
icon: "fas fa-star",
field_name: "rating_"
},
{
key: "DatePicker",
canDefaultToday: true,
canReadOnly: true,
dateFormat: "MM/dd/yyyy",
timeFormat: "hh:mm aa",
showTimeSelect: false,
showTimeSelectOnly: false,
showTimeInput: false,
name: "Date",
icon: "far fa-calendar-alt",
label: "Place holder label",
field_name: "date_picker_"
},
{
key: "Signature",
canReadOnly: true,
name: "Signature",
icon: "fas fa-pen-square",
label: "Signature",
field_name: "signature_"
},
{
key: "HyperLink",
name: "Website",
icon: "fas fa-link",
static: true,
content: "Place holder website link",
href: "http://www.example.com"
},
{
key: "Download",
name: "File attachment",
icon: "fas fa-file",
static: true,
content: "Place holder file name",
field_name: "download_",
file_path: "",
_href: ""
},
{
key: "Range",
name: "Range",
icon: "fas fa-sliders-h",
label: "Place holder label",
field_name: "range_",
step: 1,
default_value: 3,
min_value: 1,
max_value: 5,
min_label: "Easy",
max_label: "Difficult"
},
{
key: "Camera",
name: "Camera",
icon: "fas fa-camera",
label: "Place holder label",
field_name: "camera_"
},
{
key: "FileUpload",
name: "File upload",
icon: "fas fa-file",
label: "Place holder label",
field_name: "file_upload_"
}
];
};
return (
<div className="react-form-builder-toolbar">
<h4>Toolbox</h4>
<ul>
{_defaultItems().map((item, index) => {
return <ToolbarItem data={item} key={index} />;
})}
</ul>
</div>
);
};
export default Toolbar;
ToolbarItem.jsx
import React from "react";
import { useDrag } from "react-dnd";
import { ItemTypes } from "./ItemTypes";
function ToolbarItem(props) {
const data = props.data;
const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
type: ItemTypes.CARD,
collect: (monitor) => ({
isDragging: monitor.isDragging()
}),
item: props.data
}));
return (
<li ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
<i className={data.icon}></i>
{data.name}
</li>
);
}
export default ToolbarItem;
I have tried also storing the state in redux and still same behavior. For simplicity I have kept the state in parent component in Sandbox example.
In Example.js
file try replacing with this setItems((items)=>[...items, item]);
I think this will fix the issue. Check this here https://codesandbox.io/s/reactjs-dnd-example-forked-1lu5i9?file=/src/Form.jsx