I am implementing a sortable tree for a nested object using the dnd-kit-sortable-tree library. Each tree item includes an input field for editing its value. However, I am encountering an issue where the input field loses focus every time I type a character.
Details:
• The tree is managed as a nested object in state.
• Each tree item is rendered with a unique key and a ref.
• The state is updated whenever a character is typed into an input field.
Issue:
As soon as I type a character in an input field, the input loses focus. I suspect this is caused by re-renders triggered by the state update, but I am unsure how to prevent this while maintaining the sortable functionality and proper state management.
Minimal Implementation:
Here’s a simplified version of my current implementation:
import React, { useState, useRef, useEffect } from "react";
import { SimpleTreeItemWrapper, SortableTree } from "dnd-kit-sortable-tree";
import sizeof from "object-sizeof";
export default function NestedSortableTree() {
const [items, setItems] = useState(initialViableMinimalData);
console.log(sizeof(items), items, "bytes");
const handleItemChange = (itemId, newValue) => {
setItems((prevItems) => {
// Recursively find and update the item by ID
const updateItems = (nodes) =>
nodes.map((node) => {
if (node.id === itemId) {
return { ...node, value: newValue };
}
if (node.children) {
return { ...node, children: updateItems(node.children) };
}
return node;
});
return updateItems(prevItems);
});
};
return (
<SortableTree
items={items}
onItemsChanged={setItems} // For drag-and-drop updates
TreeItemComponent={(props) => (
<TreeItem {...props} onItemChange={handleItemChange} />
)}
/>
);
}
const TreeItem = React.forwardRef((props, ref) => {
console.log(props)
const inputRef = useRef(null);
const [sample, setSample] = useState(props.item.value);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus(); // Ensure input retains focus
}
}, [props.item.id]); // Trigger refocus only when the specific item changes
const onChange = (event) => {
const newValue = event.target.value;
setSample(newValue); // Update local state for immediate feedback
props.onItemChange(props.item.id, newValue); // Update the global state
};
return (
<SimpleTreeItemWrapper {...props} ref={ref} disableCollapseOnItemClick>
<input
ref={inputRef}
className="border"
value={sample}
onChange={onChange}
/>
</SimpleTreeItemWrapper>
);
});
const initialViableMinimalData = [
{
id: 1,
value: "Jane",
children: [
{ id: 4, value: "John", type: "text" },
{ id: 5, value: "Sally" },
],
},
{
id: 2,
value: "Fred",
children: [
{
id: 6,
value: "Eugene",
children: [
{ id: 7, value: "John" },
{ id: 8, value: "Sally" },
],
},
],
},
{ id: 3, value: "Helen" },
];