I have a div that renders a list of sortable sections.
Description of issues:
SortableSectionItem wont trigger drag when dragging on the drag handler as handleDragStart()
will not log the string given.
id of sortable items: ${uuidv4()}_section
Please advise which part of my implementation is erroneous with my components below:
Section renderer:
function SectionRenderer() {
const { sections, setSections } = useFormBuilderContext();
// const [dragIndex, setDragIndex] = useState(null);
const localStorageSections = JSON.parse(localStorage.getItem("sections"));
if (!localStorageSections) {
localStorage.setItem("sections", JSON.stringify(sections));
}
sections.forEach((id) => {
if (!localStorage.getItem(id))
localStorage.setItem(id, JSON.stringify({ title: "", children: [] }));
});
const handleDragStart = () => {
console.log("triggered drag on sections");
};
// const handleDragOver = ({ active, over }) => {
// const activeIndex = columns.findIndex((i) => i.key === active.id);
// const overIndex = columns.findIndex((i) => i.key === over?.id);
// setDragIndex({
// active: active.id,
// over: over?.id,
// direction: overIndex > activeIndex ? "right" : "left",
// });
// };
const handleDragEnd = ({ active, over }) => {
console.log("active: ", active);
console.log("over: ", over);
if (active.id !== over?.id) {
// console.log("triggered");
setSections((prevState) => {
const activeIndex = prevState.findIndex(active.id);
console.log("activeIndex:", activeIndex);
const overIndex = prevState.findIndex(over.id);
console.log("overIndex:", overIndex);
return arrayMove(prevState, activeIndex, overIndex);
});
}
// setDragIndex({ active: -1, over: -1 });
};
return (
<div
className="section-canvas"
style={{
flex: 12.5,
minHeight: "100%",
maxHeight: "max-content",
}}
>
<DndContext
modifiers={[restrictToVerticalAxis]}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
// onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
>
<div
className="sections-container"
style={{ display: "flex", flexDirection: "column", gap: "8px" }}
>
<SortableContext
items={sections}
strategy={verticalListSortingStrategy}
>
{sections.map((section, index) => (
<SortableSectionItem
id={section}
key={section}
index={index}
sectionId={section}
/>
))}
</SortableContext>
</div>
<DragOverlay>
{/* TODO: pass the dragged section id onto this item */}
{/* <SortableSectionItem index={null} sectionId={null} /> */}
XXXXXXXXX
</DragOverlay>
</DndContext>
</div>
);
}
Sortable Section Item:
function SortableSectionItem({ sectionId, index, id }) {
console.log("sectionId: ", id);
const { setNodeRef, attributes, listeners, transform, transition } =
useSortable({ id: id });
const [isRotate, setIsRotate] = React.useState(true);
const { sections, setSections, activeDragComponent } =
useFormBuilderContext();
const [sectionHeader, setSectionHeader] = useState(
localStorage.getItem(sectionId)
? JSON.parse(localStorage.getItem(sectionId)).title
: null
);
const currSection = JSON.parse(localStorage.getItem(sectionId));
const debouncedSectionHeader = useDebounce(sectionHeader, 300);
const handleSectionHeaderChange = (input) => {
if (currSection.title === input) return;
localStorage.setItem(
sectionId,
JSON.stringify({ ...currSection, title: input })
);
};
useEffect(() => {
handleSectionHeaderChange(debouncedSectionHeader);
}, [debouncedSectionHeader]);
const handleSectionDelete = () => {
console.log("sectionId to delete: ", sectionId);
if (sections.length <= 1) return;
setSections((prev) => {
const newSections = [...prev];
const oldSections = newSections.splice(index, 1);
localStorage.setItem("sections", JSON.stringify(newSections));
oldSections.forEach((id) => localStorage.removeItem(id));
return newSections;
});
};
const handleSectionCopy = () => {
console.log("sectionId to copy: ", sectionId);
//copying section
const copiedSection = JSON.parse(localStorage.getItem(sectionId));
// update copied section Id
const updatedCopy = { ...copiedSection, id: `${uuidv4()}_Section` };
//look for the index of the section
const idx = sections.findIndex((id) => id === sectionId);
//splice the updatedCopy into section and save to both local storage and context
setSections((prev) => {
const newSections = [...prev];
// console.log(newSections);
newSections.splice(idx + 1, 0, updatedCopy.id);
localStorage.setItem("sections", JSON.stringify(newSections));
localStorage.setItem(updatedCopy.id, JSON.stringify(updatedCopy));
return newSections;
});
};
return (
<div
className="section"
style={{
border: "1px solid rgba(0, 0, 0, 0.4)",
borderRadius: "8px",
padding: "8px",
boxSizing: "border-box",
transform: CSS.Transform.toString({
...transform,
scaleX: 1,
scaleY: 1,
}),
transition,
}}
ref={setNodeRef}
>
<div
className="section-header"
style={{
borderBottom: isRotate ? "1px solid rgba(0, 0, 0, 0.4)" : "none",
paddingBottom: "8px",
display: "flex",
gap: "8px",
justifyContent: "center",
alignItems: "center",
}}
>
<MdOutlineExpandMore
style={{
rotate: isRotate ? "0deg" : "-90deg",
cursor: "pointer",
}}
onClick={() => setIsRotate(!isRotate)}
/>
<div>{`${index + 1}.`}</div>
<Input
id={sectionId}
size="small"
maxLength={200}
allowClear
value={sectionHeader}
onChange={(e) => setSectionHeader(e.target.value)}
/>
<MdFileCopy
style={{ cursor: "pointer", fontSize: "14px" }}
onClick={handleSectionCopy}
/>
<MdOutlineDelete
// ref={ref}
style={{
cursor: "pointer",
// color: hovering ? "red" : "none", TODO: to forward ref
}}
onClick={handleSectionDelete}
/>
<Divider type="vertical" style={{ margin: "0px 4px" }} />
<FiMove style={{ cursor: "move" }} {...attributes} {...listeners} />
</div>
{isRotate && (
<div
className="section-content"
style={{
paddingTop: "8px",
display: "flex",
flexDirection: "column",
gap: "2px",
}}
>
<div className="section-content-render-container">
Area for component to render
</div>
<DroppableSectionArea sectionId={sectionId} />
</div>
)}
</div>
);
}