React Hook Form Multiple Dropdown Validation

Currently I am using semantic ui css multiple dropdown validation. How do I validate the dropdown so that user has to choose at least ONE option before submitting ?

Right now the current output is that if I submit the form WITHOUT choosing any option, the error message appears.

However, if I choose an option and the submit, the error message still appears.

enter image description here

Following is my code.

import React, { useState } from 'react'
import { Button, Checkbox, Form, Dropdown } from 'semantic-ui-react'
//validation dependencies
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';


function TestForm() {

    const validationSchema = Yup.object().shape({
        firstName: Yup.string()
            .required('firstName is required')
            .min(6, 'firstName must be at least 6 characters')
        ,
        options: Yup
            .object()
            .shape({
                key: Yup.number().required("key is required (from label)"),
                value: Yup.string().required("Value is required")
            })
            .nullable() // for handling null value when clearing options via clicking "x"
            .required("options is required (from outter null check)")
    });
    const formOptions = { mode: 'all', reValidateMode: 'onChange', resolver: yupResolver(validationSchema) };
    const { register, handleSubmit, reset, formState: { errors }, watch, setValue, control } = useForm(formOptions);
    const [stateOptions, setStateOptions] = useState([
        {
            key: 1,
            text: 'aaaa',
            value: 'a'
        },
        {
            key: 2,
            text: 'bbbb',
            value: 'b'
        },
        {
            key: 3,
            text: 'cccc',
            value: 'c'
        },
    ]);
    console.log(watch())//For Debugging

    const onSubmitHandler = (data) => {
        console.log({ data });
        setValue('firstName', 'dash', { shouldDirty: true, shouldValidate: true })
    }

    return (
        <div>
            <Form reply onSubmit={handleSubmit(onSubmitHandler)}>
                <Form.Field>
                    <label>First Name</label>
                    <input placeholder='First Name' name="firstName" {...register('firstName')} />
                    <div className="invalid-feedback">{errors.firstName?.message}</div>
                </Form.Field>
                <Form.Field>
                    <Controller
                        name="options"
                        control={control}
                        render={({ field }) => (
                            <Dropdown
                                {...field}
                                placeholder='State'
                                fluid
                                multiple
                                search
                                selection
                                options={stateOptions}
                            />
                        )}
                    />
                    <div className="invalid-feedback">{errors.options?.message || errors.options?.value.message}</div>
                </Form.Field>
                <Form.Field>
                    <Button type='submit'>Submit</Button>
                </Form.Field>
            </Form>
        </div>
    )
}

export default TestForm

Solution 1:

Since Dropdown component in semantic-ui-react doesn't support ref attribute, so you need to controll the value of DropDown by yourself, here is an example you can try on it:

 const options = [
  {
    key: 1,
    text: 'aaaa',
    value: 'a',
  },
  {
    key: 2,
    text: 'bbbb',
    value: 'b',
  },
  {
    key: 3,
    text: 'cccc',
    value: 'c',
  },
]

function TestForm() {
  const validationSchema = Yup.object().shape({
    firstName: Yup.string()
      .required('firstName is required')
      .min(6, 'firstName must be at least 6 characters'),
    options: Yup.array()
    .of(Yup.object()
      .shape({
        key: Yup.number().required('key is required (from label)'),
        value: Yup.string().required('Value is required'),
      }))
      .test(
        "required",
        "options is required",
        (value) => Array.isArray(value) && value.length > 0
      ),
  });
  const formOptions = {
    mode: 'all',
    reValidateMode: 'onChange',
    resolver: yupResolver(validationSchema),
  };
  const {
    register,
    handleSubmit,
    formState: { errors },
    setValue,
    control,
  } = useForm(formOptions);

  const onSubmitHandler = (data) => {
    console.log(data);
  };

  return (
    <div>
      <Form reply onSubmit={handleSubmit(onSubmitHandler)}>
        <Form.Field>
          <label>First Name</label>
          <input
            placeholder="First Name"
            name="firstName"
            {...register('firstName')}
          />
          <div className="invalid-feedback">{errors.firstName?.message}</div>
        </Form.Field>
        <Form.Field>
          <Controller
            name="options"
            control={control}
            render={({ field }) => {
              let { value, ...other } = field;
              return (
                  <Dropdown
                    {...other}
                    placeholder="State"
                    fluid
                    multiple
                    search
                    selection
                    options={options}
                    value={Array.isArray(value) ? value.map(v => v.value) : []}
                    onChange={(e,{value})=> {
                      const values = value.map(v => options.find(item => item.value == v));
                      setValue('options', values)
                    }}
                  />
              );
            }}
          />
          <div className="invalid-feedback">
            {errors.options?.message || errors.options?.value.message}
          </div>
        </Form.Field>
        <Form.Field>
          <Button type="submit">Submit</Button>
        </Form.Field>
      </Form>
    </div>
  );
}