react-component / select

React Select
https://select.react-component.now.sh/
MIT License
898 stars 454 forks source link

`<Select>` component `<form>` compatability #799

Open cliffordfajardo opened 2 years ago

cliffordfajardo commented 2 years ago

Description

Currently <Select> component is not fully <form> compatible, which means when you submit a <Form> or <form> the data from the <Select> is not included in the form submission.



How to Reproduce Bug

Submit a form which contain's <Select> (see video below or see codesandbox here)



Motivation For Changes

1. HTML <form> compatibility

2. Many of Ant design's existing form-related components are already <form> compliant

  1. Removes the need for a user of Ant Design to manually add hidden input fields . This is the same issue Tailwind Headless UI had but solved in this pull request



Proposition of Changes

The proposed changes below are backwards compatible:

  1. allow user to pass name prop to <Select> to achieve feature compliance with regular<select> which does allow name prop (Ex: <select name="vehicle"> ).

    CleanShot 2022-07-19 at 08 46 57@2x
  2. When a user passes name prop to <Select> generate a hidden input (<input type="hidden" name={selectProps.name}>) and when the user selects the value from the list, add value attribute to the hidden input and set it to the selected value (<input type="hidden" name={selectProps.name} value={selected_value}>. Inspiration for this implementation comes from Tailwind's Headless UI react library (Github Pull Request)

For single item selection (fake code for demonstrative purpose)

<Select showSearch style={{ width: 300 }} name="job_title">
    <Select.Option value="engineer">engineer</Select.Option>
    <Select.Option value="teacher">teacher</Select.Option>
      // This hidden input would get generated from <Select> when a `name` prop is provided
     // <input type="hidden" name={select.props.name} value={the_selected_value}>
</Select>

For multi selection (fake code for demonstrative purpose) mode=tags | multiple

<Select showSearch style={{ width: 300 }} name="job_title">
    <Select.Option value="engineer">engineer</Select.Option>
    <Select.Option value="teacher">teacher</Select.Option>
      // Any time a new item is selected a new  hidden input is generated from <Select> when a `name` prop is provided
     // <input type="hidden" name={select.props.name} value={the_selected_value}>
     // <input type="hidden" name={select.props.name} value={the_selected_value}>
</Select>



Summary

To solve this, <Select> needs name prop support & it needs to generate a hidden <input> element for the selected value


References

Documentation for <form> comptability

Related Issues

chalkedgoose commented 2 years ago

native form compatibility would be a big win!

manzaloros commented 2 years ago

The more compatibility the better. I didn't realize there wasn't native <form> compatibility until this issue was raised.

guepjo commented 2 years ago

Encountered this issue a couple weeks ago and couldn't find any work around. Adding this feature would be greatly appreciated!

kevinreber commented 2 years ago

Thanks for documenting all this @cliffordfajardo The proposed changes would also help out a lot with e2e testing.

The way the current Select component is generated in the DOM, makes it tricker to select the Select component and verify values in e2e testing.

Example

React Code

// AntDesign Select component
<Form.Item label="AntDesign Selector" name="Ant-Selector-FormItem">
  <Select data-testid="Ant-Selector">
    <Select.Option value="YES">YES</Select.Option>;
    <Select.Option value="NO">NO</Select.Option>;
  </Select>
</Form.Item>

DOM

// AntDesign Select component Generated in DOM
<div class="ant-select ant-select-single ant-select-show-arrow" data-testid="Ant-Selector">
  <div class="ant-select-selector">
    <span class="ant-select-selection-search">
      <input type="search" id="Ant-Selector-FormItem" autocomplete="off" class="ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="Ant-Selector_list" aria-autocomplete="list" aria-controls="Ant-Selector_list" aria-activedescendant="Ant-Selector_list_0" readonly="" unselectable="on" value="" style="opacity: 0;">
    </span>
    <span class="ant-select-selection-item" title="YES">YES</span>
  </div>
  <span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;">
    <span role="img" aria-label="down" class="anticon anticon-down ant-select-suffix">
      <svg viewBox="64 64 896 896" focusable="false" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true">
        <path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path>
      </svg>
    </span>
  </span>
</div>

e2e Testing using Playwright

// e2e Test for Selector component
  test('Get AntDesign Selector value', async ({ page }) => {

    // When clicking on a Selector component in AntDesign, the value is not stored in the input element
    // AntDesign creates a new span element, that stores/renders the selected value
    // We need to check this span to ensure the value is updated
    const AntSelectorValue = page.locator(
      '[data-testid="Ant-Selector"] .ant-select-selector>span.ant-select-selection-item',
    );

    // Ideally we would want to use `.inputValue()` instead of `.innerText()`,
    // but since our `Select` value is stored in a `span` we can NOT use `.inputValue()`
    expect(await AntSelectorValue.innerText()).toBe('YES');
    expect(await AntSelectorValue.innerText()).not.toBe('NO');
  });