Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React useState doesn't update correctly #99

Open
cstrat opened this issue Nov 6, 2023 · 3 comments
Open

React useState doesn't update correctly #99

cstrat opened this issue Nov 6, 2023 · 3 comments

Comments

@cstrat
Copy link

cstrat commented Nov 6, 2023

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.

2023-11-06 15 00 11

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?

@cstrat
Copy link
Author

cstrat commented Nov 6, 2023

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>
  );
}

@cstrat
Copy link
Author

cstrat commented Nov 6, 2023

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.

@MoltenCoffee
Copy link

MoltenCoffee commented Dec 15, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants