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

New onCreate behavior for Select component creates challenges #2095

Closed
apoco opened this issue Aug 12, 2022 · 4 comments
Closed

New onCreate behavior for Select component creates challenges #2095

apoco opened this issue Aug 12, 2022 · 4 comments
Labels
Fixed patch Completed issues that will be published with next patch (1.0.X)

Comments

@apoco
Copy link
Contributor

apoco commented Aug 12, 2022

What package has an issue

@mantine/core

Describe the bug

I'm working on upgrading from Mantine 4 to 5, and one of the changes introduces challenges. To explain, let's consider this sample component that worked in Mantine 4:

const BoatPicker = ({ value, boats, onAdd, onSelect }) => {
  const selectItems = useMemo(() => {
    return boats.map(boat => ({ value: boat.id, label: boat.name }));
  }, [boats]);

  const lookupById = useMemo(() => {
    return new Map(() => boats.map(boat => [boat.id, boat]));
  }, [boats]);

  return (
    <Select
      searchable
      creatable
      value={value?.id}
      data={selectItems}
      getCreateLabel={(name) => `${name} (new)`}
      onChange={value => onSelect(lookupById.get(value))}
      onCreate={text => {
        const newBoat = { id: generateId(), name: text };
        onAdd(newBoat);
        onSelect(newBoat);
      }}
    />
  );
}

When a user typed a new value, this sequence of events would take place.

  1. onCreate was called
  2. onAdd was called with a new Boat object, and the parent component would do its thing and update its underlying data
  3. onSelect was called with the new Boat object, and the parent component would update its state accordingly
  4. Component would re-render, with a new value prop set to the new item, and a new boats property with the list including the new item.
  5. selectItems would be updated due to the change of the boats property
  6. lookupById would be updated due to the change of the boats property
  7. The new Select is rendered with the new data and value

With 5.x we have to return a new SelectItem in our callback, and the onChange callback is called immediately after onCreate. Even if onCreate does return { value: newBoat.id, label: newBoat.name }; at the end, this sequence happens:

  1. onCreate is called
  2. onAdd is called with a new Boat object, and the parent component does its updates
  3. onSelect is called with the new Boat object, and the parent component updates its state accordingly
  4. onChange is called with the new item's value
  5. The new value passed to onChange is not present in lookupById because a re-render has not yet happened, so onSelect is called with undefined.
  6. Component re-renders, with a new value prop set to undefined and a new boats property with the list including the new item.

So after this sequence the object we just created is not selected because on step 5 the call to onChange undoes the selection we performed on step 3.

What version of @mantine/hooks page do you have in package.json?

5.1.4

If possible, please include a link to a codesandbox with the reproduced problem

https://codesandbox.io/s/kind-lamport-noc5ez?file=/src/App.tsx

Do you know how to fix the issue

Yes

Are you willing to participate in fixing this issue and create a pull request with the fix

Yes

Possible fix

Thankfully for me there's a workaround for the race condition because I can just assume that if a matching value is not in my lookup Map that this create race condition took place, but I wonder if there's a cleaner approach that introduces less surprises.

I'm guessing that the reason you require a return value from onCreate is for cases where the data prop is not fully controlled. Thus, it makes sense that if Mantine is going to manage the state of the new item you'd have to supply it. Similarly, it'd make sense that onChange would be called immediately to select that newly injected item.

For those of us that are treating this as a controlled component, maybe it'd make sense to allow not returning a value in onCreate or support returning false to indicate that we're handling our own insertion and selection logic. If undefined (or false) is returned, onChange wouldn't be called (this would allow let us support the case where new item creation is explicitly failing), but if you do return a value the current behavior would persist.

Another thing that could work is if the onChange callback on creation of a new item has a second boolean parameter indicating that it is the selection of a new item.

Thoughts?

@rtivital
Copy link
Member

Hi @apoco, inside onCreate handler, you must do two things:

  • update data array
  • return newly created item

onChange within onCreate is triggered automatically with new value, it means that it is required to update data array before it is called (like in the docs example – https://mantine.dev/core/select/#creatable). I cannot fully understand the issue in your sandbox, but I've fixed it for you using an example from documentation – https://codesandbox.io/s/spring-cdn-ps9y7z?file=/src/App.tsx

@apoco
Copy link
Contributor Author

apoco commented Aug 14, 2022

Hm, your changes don't fix the issue. Maybe you didn't save your sandbox? I understand the documentation, but notice that when you choose the created item that it still shows null as the selected value below the Select. That is because onChange is called right away before any changes to state have taken place, so the lookup of the Boat during the onChange event fails.

@rtivital
Copy link
Member

Alright, I get the issue now, you want to get new data in onChange handler. I think we can add an option of returning null that will indicate that onCreate should not call onChange.

@rtivital rtivital added the Fixed patch Completed issues that will be published with next patch (1.0.X) label Aug 18, 2022
rtivital added a commit that referenced this issue Aug 18, 2022
…nd MultiSelect when onCreate function returns null or undefined (#2095)
@rtivital
Copy link
Member

Fixed in 5.1.7, now when onCreate function returns null or undefined onChange is not called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed patch Completed issues that will be published with next patch (1.0.X)
Projects
None yet
Development

No branches or pull requests

2 participants