skip to Main Content

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?

2

Answers


  1. Chosen as BEST ANSWER

    Using Form.useForm()

    A solution I found is using the hook Form.useForm()

    //Add a prop to pass the onChangeEvent
    const DropdownCustom = ({onSelectChange = null}) => {
         //Custom code here....
      return (
        <Select
          //pass the prop directly to the Select component
          onChange={onSelectChange}
          style={{
            width: 300,
          }}
          placeholder="custom dropdown render"
          dropdownRender={(menu) => (
            <>
               {/*Any template of the dropdown you want*/}
            </>
          )}
          options={items.map((item) => ({
            label: item,
            value: item,
          }))}
        />
      );
    };
    
    const App = () => {
    //get the useForm hook
       const [form] = Form.useForm();
      //set manually the value 
        const handleSelectChange = (value) => {
        if (value) {
          form.setFieldsValue({ option: value });
        }
      };
    
        //Rest of your code here...
      
      return <Form
        {/*set the hook to the form*/}
        form={form}
        {/*more form props*/}>
        <Form.Item label="Choose option" name="option" rules={[{required:true,message:"Please, choose an option"}]}>
            <DropdownCustom onSelectChange={handleSelectChange}/>    
        </Form.Item>
         {/*Rest of the form body*/}
      </Form>
    };
    const ComponentDemo = App;
    
    createRoot(mountNode).render(<ComponentDemo />);
    
    

    You can try the new example here


  2. 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?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search