Search code examples
reactjsformsvalidationselectantd

Ignore dropdown render Input item in a Select item - AntDesign


AntDesign Form

I want to use a "Select item" with a Custom Dropdown render for show a preloaded data, and you can add a new item if doesn't exists.

The problem resides on submit the form, because the dropdown render has an "Input item", and takes the value of the Input, instead of the Select.

Codepen of the code with the problem

const DropdownCustom = () => {
  const [items, setItems] = useState(['jack', 'lucy']);
  const [name, setName] = useState('');
  const inputRef = useRef(null);
  const onNameChange = (event) => {
    setName(event.target.value);
  };
  const addItem = (e) => {
    e.preventDefault();
    setItems([...items, name || `New item ${index++}`]);
    setName('');
    setTimeout(() => {
      inputRef.current?.focus();
    }, 0);
  };
  return (
    <Select
      style={{
        width: 300,
      }}
      placeholder="custom dropdown render"
      dropdownRender={(menu) => (
        <>
          {menu}
          <Divider
            style={{
              margin: '8px 0',
            }}
          />
          <Space
            style={{
              padding: '0 8px 4px',
            }}
          >
            <Input              
              placeholder="Please enter item"
              ref={inputRef}
              value={name}
              onChange={onNameChange}
              onKeyDown={(e) => e.stopPropagation()}
            />
            <Button type="text" icon={<PlusOutlined />} onClick={addItem}>
              Add item
            </Button>
          </Space>
        </>
      )}
      options={items.map((item) => ({
        label: item,
        value: item,
      }))}
    />
  );
};

Is there a way to set the Input to add new data and prioritize the value of the Select Item?


Solution

  • When you pass name prop to Form.Item, antd will clone the children and pass two props to the children value and onChange. If you can verify it by accepting props in CustomDropdown and console it. You just need to pass those props to <Select component to make it controlled. Your Input has nothing to do with the dropdown.

    Here's the complete code.

    import { PlusOutlined } from '@ant-design/icons';
    import { Button, Checkbox, Divider, Form, Input, Select, Space } from 'antd';
    import { useRef, useState } from 'react';
    
    const DropdownCustom = (props) => {
        console.log(props); // => value, onChange
    
        const [items, setItems] = useState(['jack', 'lucy']);
        const [name, setName] = useState('');
        const inputRef = useRef(null);
        const onNameChange = (event) => {
            setName(event.target.value);
        };
        const addItem = (e) => {
            e.preventDefault();
            setItems([...items, name || `New item ${index++}`]);
            setName('');
            setTimeout(() => {
                inputRef.current?.focus();
            }, 0);
        };
        return (
            <Select
                {...props}
                style={{ width: 300 }}
                placeholder='custom dropdown render'
                dropdownRender={(menu) => (
                    <>
                        {menu}
                        <Divider style={{ margin: '8px 0' }} />
                        <Space style={{ padding: '0 8px 4px' }}>
                            <Input
                                placeholder='Please enter item'
                                ref={inputRef}
                                value={name}
                                onChange={onNameChange}
                                onKeyDown={(e) => e.stopPropagation()}
                            />
                            <Button type='text' icon={<PlusOutlined />} onClick={addItem}>
                                Add item
                            </Button>
                        </Space>
                    </>
                )}
                options={items.map((item) => ({ label: item, value: item }))}
            />
        );
    };
    
    const onFinish = (values) => {
        console.log('Success:', values);
    };
    const onFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo);
    };
    
    const App = () => (
        <Form
            name='basic'
            labelCol={{ span: 8 }}
            wrapperCol={{ span: 16 }}
            style={{ maxWidth: 600 }}
            initialValues={{ remember: true }}
            onFinish={onFinish}
            onFinishFailed={onFinishFailed}
            autoComplete='off'
        >
            <Form.Item label='Username' name='username' rules={[{ required: true, message: 'Please input your username!' }]}>
                <Input />
            </Form.Item>
    
            <Form.Item label='Password' name='password' rules={[{ required: true, message: 'Please input your password!' }]}>
                <Input.Password />
            </Form.Item>
            <Form.Item label='Choose option' name='option' rules={[{ required: true, message: 'Please, choose an option' }]}>
                <DropdownCustom />
            </Form.Item>
            <Form.Item name='remember' valuePropName='checked' wrapperCol={{ offset: 8, span: 16 }}>
                <Checkbox>Remember me</Checkbox>
            </Form.Item>
    
            <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
                <Button type='primary' htmlType='submit'>
                    Submit
                </Button>
            </Form.Item>
        </Form>
    );
    

    You can also follow antd api for more examples.

    Why Form.Item not update value when children is nest?