react-movable
react-movable copied to clipboard
React useState doesn't update correctly
Unsure if I am doing something wrong here, but I have an issue where my state isn't being updated in the onChange
hook.
I get a weird effect where after dragging and dropping, the list looks like it did prior to the drag & drop. If I try to drag the list item from where I left it, it transforms into the old element.
See the example above.
I tried searching for a similar issue in the issues here, and noticed someone else implemented a setTimeout
which actually fixed it, but then created a flash of missing content in my modal which is not desirable...
Am I doing something wrong here?
This is the code I am using. Using Mantine library for UI.
I originally had the data in a parent component and passed the state and state updater to this one, but thought that was my issue. So now I am saving local state and not even worrying about the parent state.
function LicenseEditor({ showLicence, licenseData, user }) {
const [localLD, setLocalLD] = useState(licenseData);
return (
<Fieldset mb="md" legend="License Editor">
<List
values={localLD}
onChange={({ oldIndex, newIndex }) => setLocalLD(arrayMove(localLD, oldIndex, newIndex))}
renderList={({ children, props, isDragged }) => (
<Table
striped
highlightOnHover
withColumnBorders
style={{
fontSize: `${user.preferences?.adminTableSize || 0.85}rem`,
cursor: isDragged ? "grabbing" : undefined,
}}
>
<Table.Thead>
<Table.Tr>
<Table.Th style={{ width: "30%" }}>SKU</Table.Th>
<Table.Th style={{ width: "35%" }}>Name</Table.Th>
<Table.Th style={{ width: "20%" }}>Term</Table.Th>
<Table.Th style={{ width: "15%" }}></Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
<Table.Td>
<TextInput
name="sku"
placeholder="License SKU"
/>
</Table.Td>
<Table.Td>
<Autocomplete
name="name"
placeholder="Friendly Name"
/>
</Table.Td>
<Table.Td>
<NumberInput
step={12}
name="term"
placeholder="Term in Months"
/>
</Table.Td>
<Table.Td>
<Button fullWidth size="compact-sm" onClick={addNewLicence}>
Add
</Button>
</Table.Td>
</Table.Tr>
</Table.Tbody>
<Table.Tbody {...props}>{children}</Table.Tbody>
</Table>
)}
renderItem={({ value, props, isDragged, isSelected }) => {
const row = (
<Table.Tr key={value.id} {...props} style={{ cursor: isDragged ? "grabbing" : "grab" }}>
<Table.Td>
<input type="hidden" name="licenseID" value={value.id} />
<TextInput name="licenseSKU" defaultValue={value.sku} placeholder="License SKU" />
</Table.Td>
<Table.Td>
<Autocomplete
name="licenseName"
defaultValue={value.name}
placeholder="Friendly Name"
data={[...localLD.map((lic) => lic.name).filter((v, i, a) => a.indexOf(v) === i)]}
/>
</Table.Td>
<Table.Td>
<NumberInput name="licenseTerm" defaultValue={value.term} step={12} placeholder="Term in Months" />
</Table.Td>
<Table.Td>
<Group grow>
<Button variant="light" color="blue" size="compact-sm" data-movable-handle>
<IconGripHorizontal size="1.2rem" />
</Button>
<Button variant="light" color="blue" size="compact-sm">
<IconTrash size="1.2rem" />
</Button>
</Group>
</Table.Td>
</Table.Tr>
);
return isDragged ? (
<Table style={{ ...props.style, borderSpacing: 0, zIndex: 205 }}>
<Table.Tbody>{row}</Table.Tbody>
</Table>
) : (
row
);
}}
/>
</Fieldset>
);
}
I also tried stripping this back to just regular <table>
- even though that wouldn't make sense to fix things, just because I was pulling my hair out... and it still happens.
I had a similar issue which I've struggled with for a few hours. Not entirely sure if it's the same, and also not entirely sure whether it was a bug in the library, or in the way React handles arrays/children, or the way browsers keep track of input elements.
In my case, I had a form, to which the user can add new input elements (textareas) and move them around. I was using controlled components. On creation, value
would be left empty, and an onChange
would store the value in component state when the user changed it contents for the first time, and of course thereafter. When I would add a new (and thus, empty) textarea to the form, and move it around, it would copy the value of a different field, but no onChange
event would be fired and no state stored.
In my case, the solution was to store the value of a newly created textarea as an empty string in state.
Seeing as your example used defaultValue
, I'd assume something similar is/was happening to your code.
Edit: React 18.2.0, in both Chrome 120 and Firefox 114
You have to keep the state of dragged components outside of those components.