Search code examples
reactjssemantic-uireact-hook-formyup

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

  • 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>
      );
    }