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

Problem when using onAddItem in Combox #799

Closed
FredrikMWold opened this issue Oct 14, 2024 · 12 comments
Closed

Problem when using onAddItem in Combox #799

FredrikMWold opened this issue Oct 14, 2024 · 12 comments

Comments

@FredrikMWold
Copy link
Contributor

When trying to add a new value using the onAddItem function of the Combox and pressing enter. Exisitng values gets removed or replaced.

Replicate:
1, Select one or more existing value
2. Type in a new value and press enter

Image

@FredrikMWold
Copy link
Contributor Author

It looks like the issue is inconsistency in when onSelect is called.
When typing a new item and clicking it add with a mouse only the onAddItem callback gets called, but if you press enter both onSelect and onAddItem get called.

@mariush2
Copy link
Contributor

Added this to our backlog as well and will take a look at it 😄

@mariush2
Copy link
Contributor

I wasn't able to reproduce this in the storybook or in a vitest unit test, do you still have this issue?

For referance, here is the unit test i wrote:

test('OnAddItem works as expected with {Enter} and preselected values', async () => {
  const handleOnAddItem = vi.fn();
  const handleOnSelect = vi.fn();
  const items = fakeSelectItems();
  render(
    <ComboBox
      values={[items[0]]}
      onSelect={handleOnSelect}
      onAddItem={handleOnAddItem}
      items={items}
    />
  );
  const user = userEvent.setup();

  const input = screen.getByRole('combobox');

  const someRandomText = faker.vehicle.vehicle();

  await user.click(input);

  await user.type(input, someRandomText);

  await user.keyboard('{Enter}');

  expect(handleOnAddItem).toHaveBeenCalledWith(someRandomText);
  expect(handleOnSelect).not.toHaveBeenCalled();
});

@FredrikMWold
Copy link
Contributor Author

FredrikMWold commented Oct 18, 2024

Steps to repoduce:

  1. add a console log inside onItemAdd and onSelect
  2. click input field and type something new
  3. press enter without navigateing with arrow keys

When this is done both onSelect and onAddItem.
Which is causing race conditions in my code.

Tested with: 8.9.2

@FredrikMWold
Copy link
Contributor Author

Did not mean to close the issue

@FredrikMWold FredrikMWold reopened this Oct 18, 2024
@mariush2
Copy link
Contributor

https://github.com/user-attachments/assets/42c3c2a5-5620-455d-9b08-e742e1e822f4
Still not able to reproduce this, it is just calling the handlers once in the storybook which looks like this:

export const ComboBoxWithAdd: StoryFn = (args) => {
  const [values, setValues] = useState<SelectOption<Item>[]>([]);
  const handleOnSelect = (newValues: SelectOptionRequired[]) => {
    actions('onSelect').onSelect(newValues);
    setValues(newValues);
  };

  const handleOnAdd = (value: string) => {
    actions('onItemAdd').onItemAdd(value);
    const newItem = {
      label: value,
      value: faker.string.uuid(),
    };

    setValues((prev) => [...prev, newItem]);
  };

  return (
    <ComboBox
      {...args}
      values={values as SelectOptionRequired[]}
      items={FAKE_ITEMS}
      onSelect={handleOnSelect}
      onAddItem={handleOnAdd}
    />
  );
};

@FredrikMWold
Copy link
Contributor Author

How do you know in storybook that only the handleOnAdd get called when typing something that does not exsist and cliking enter?
It is calling the handlers once for me to the problem is that it is calling both onSelect and handleOnAdd once in some cases

Have you tried adding console.los like this:

export const ComboBoxWithAdd: StoryFn = (args) => {
  const [values, setValues] = useState<SelectOption<Item>[]>([]);
  const handleOnSelect = (newValues: SelectOptionRequired[]) => {
    actions('onSelect').onSelect(newValues);
    console.log('onSelect')
    setValues(newValues);
  };

  const handleOnAdd = (value: string) => {
    actions('onItemAdd').onItemAdd(value);
    const newItem = {
      label: value,
      value: faker.string.uuid(),
    };
    console.log('handleOnAdd')
    setValues((prev) => [...prev, newItem]);
  };

  return (
    <ComboBox
      {...args}
      values={values as SelectOptionRequired[]}
      items={FAKE_ITEMS}
      onSelect={handleOnSelect}
      onAddItem={handleOnAdd}
    />
  );
};```

@FredrikMWold
Copy link
Contributor Author

It looks like the problem might be that your example is very basic and is not a controlled component. Its seems like the items props i reactive and when i changes it runs onAddItem.

@FredrikMWold
Copy link
Contributor Author

<ComboBox
	label='Tags'
	items={[
		{
			label: 'test',
			value: 'test',
		},
	]}
	values={[{ label: 'test', value: 'test' }]}
	onSelect={(selectedItems: SelectOptionRequired[]) => {
		console.log('onSelect');
	}}
	onAddItem={(tag: string) => {
		console.log('onAddItem');
	}}
	placeholder='Select tags...'
/>

This should produce the error. Calling both onSelect and onAddItem

@mariush2
Copy link
Contributor

The example you included still only runs onAddItem when I type and hit enter to create a new item, this is the exact code in the story in the video:

export const ComboBoxWithAdd: StoryFn = (args) => {
  const [values, setValues] = useState<SelectOption<Item>[]>([]);
  const handleOnSelect = (newValues: SelectOptionRequired[]) => {
    actions('onSelect').onSelect(newValues);
    setValues(newValues);
  };

  const handleOnAdd = (value: string) => {
    actions('onItemAdd').onItemAdd(value);
    const newItem = {
      label: value,
      value: faker.string.uuid(),
    };

    setValues((prev) => [...prev, newItem]);
  };

  return (
    <ComboBox
      label="Tags"
      items={[
        {
          label: 'test',
          value: 'test',
        },
      ]}
      values={[{ label: 'test', value: 'test' }]}
      onSelect={(selectedItems: SelectOptionRequired[]) => {
        console.log('onSelect');
      }}
      onAddItem={(tag: string) => {
        console.log('onAddItem');
      }}
      placeholder="Select tags..."
    />
  );
};
Skjermopptak.2024-10-21.kl.11.29.04.mov

I tried creating a controlled version as well where the items are updated with setState and it still only calls the handler once:

export const ComboBoxWithAdd: StoryFn = (args) => {
  const [items, setItems] = useState([...FAKE_ITEMS]);
  const [values, setValues] = useState<SelectOption<Item>[]>([]);
  const handleOnSelect = (newValues: SelectOptionRequired[]) => {
    actions('onSelect').onSelect(newValues);
    setValues(newValues);
    console.log('onSelect');
  };

  const handleOnAdd = (value: string) => {
    actions('onItemAdd').onItemAdd(value);
    const newItem = {
      label: value,
      value: faker.string.uuid(),
    };
    console.log('onAddItem');

    // NEW
    setItems((prev) => [...prev, newItem]);
    setValues((prev) => [...prev, newItem]);
  };

  return (
    <ComboBox
      {...args}
      values={values as SelectOptionRequired[]}
      items={items}
      onSelect={handleOnSelect}
      onAddItem={handleOnAdd}
    />
  );
};
Skjermopptak.2024-10-21.kl.11.32.09.mov

@FredrikMWold
Copy link
Contributor Author

What version are you on?

@mariush2
Copy link
Contributor

Fix will be released in 8.10.2 🎉

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