Dmitry Stril
Dmitry Stril

Reputation: 1465

How do I implement field validation for 'react-select'

I need a simple "required" validation for 'react-select' (github repo). In the latest version it uses css-in-js approach. So I have custom styles:

export const customStyles = {


 control: (base, state) => ({
        ...base,
    }),

    menu: (base, state) => ({
        ...base,
    }),

    menuList: (base, state) => ({
        ...base,
    }),
}

How can I change e.g. borderColor if field is invalid?

Upvotes: 6

Views: 49938

Answers (6)

tim-montague
tim-montague

Reputation: 17372

React-Select style for Bootstrap 5 validation

class CustomSelect extends React.Component {
  render() {
    const { valid, invalid } = this.props;

    let borderColor = '#ced4da';
    let focusBorderColor = '#66afe9';
    let focusBoxShadow = '0 0 0 .2rem rgba(0, 123, 255, 0.25)';
    if (valid) {
      borderColor = '#198754';
      focusBorderColor = '#198754';
      focusBoxShadow = '0 0 0 .2rem rgba(25, 135, 84, .25)';
    } else if (invalid) {
      borderColor = '#dc3545';
      focusBorderColor = '#dc3545';
      focusBoxShadow = '0 0 0 .2rem rgba(220, 53, 69, .25)';
    }

    const customStyles = {
      valueContainer: (provided, state) => ({
        ...provided,
        borderColor: state.selectProps.menuIsOpen ? focusBorderColor : borderColor,
        boxShadow: state.selectProps.menuIsOpen ? focusBoxShadow : 'none',
      }),
    };

    return (
      <Select styles={customStyles} {...this.props} />
    );
  }
}

Upvotes: 0

Maniruzzaman Akash
Maniruzzaman Akash

Reputation: 5035

A help for someone who doesn't want to add all time some codes for only of this required validation in react-select. Just use react-hook-form-input.

<RHFInput
   as={<Select options={options} />}
   rules={{ required: 'Please select an option'}}
   name="reactSelect"
   register={register}
   setValue={setValue}
/>

Where this RHFInput from react-hook-form-input was just a save for me.. Complete example- react-hook-form-input.

import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';

const options = [
  { value: 'chocolate', label: 'Chocolate' },
  { value: 'strawberry', label: 'Strawberry' },
];

function App() {
  const { handleSubmit, register, setValue, reset, errors } = useForm();

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <RHFInput
        as={<Select options={options} />}
        rules={{ required: 'Please select an option'}}
        name="reactSelect"
        register={register}
        setValue={setValue}
      />
      <span className="text-danger">
        {errors.reactSelect && errors.reactSelect.type === 'required' && "Please select"}
      </span>

      <button type="button">Reset Form</button>
      <button>submit</button>
    </form>
  );
}

Hope, it will help someone like me as a beginner in react.

Upvotes: 4

MHL007
MHL007

Reputation: 49

look at this link:https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/index.js

you have to use Controller

   <Controller
          control={control}
          rules={{ required: true }}
          name="citySelect"
          getOptionLabel={(option) => option.name}
          getOptionValue={(option) => option.id}
          options={citiesOption}
          value={city}
          onChange={handleCity}
          className="basic-single"
          classNamePrefix="select"
          isRtl
          placeholder="استان مورد نظر"
          as={Select}
        />

        {errors.citySelect && <p>choose a city</p>}

Upvotes: 0

Mike Thomson
Mike Thomson

Reputation: 1

The best way I found is to create a transparent input field that will be queried via javascript standard checkValidity. This should have absolute positioning and 100% width & height. You can then bind a listener to the input field for the invalid event created by checkValidity

Here is the code I use. There are alterations for use of value field as well as some styling (for MDB inputs) , but you can just change the classes of input to match your own styles libraries. In this manner your validation styling will be the same as your existing form inputs.

Hopes this helps someone.

    /**************************************************************************************
 *** Select - New Select Control Using react-select (to stop stupid probs with MDB) ***
 **************************************************************************************/
// Use This progressively instead of InputSelect
/* Differences from native ReactSelect 
    Validation - Applies transparent input overlay that works with form checkValidity()
    value: This is the record.field value not an object {label: x, value: y} as for react-select
    grouped: Explicitly set grouped options when set true
    objValue: Works the same as react-select value object
*/
// Note: value property works differently do react-select , use ObjValue to work same as react-select
export const Select = (props) => {
  let { id, options, cols, value, objValue, label, grouped, ...o } = props
  id = id ? id : 'react-select'
  const [selected, setSelected] = useState(value)
  const [invalid, setInvalid] = useState(false)

  //DEFAULTS
  if (!grouped) grouped = false

  //--------------------------
  // VALIDATION EVENT HANDLERS
  //--------------------------
  useEffect(() => {
    //ADD EVENT HANDLER FOR FOR VALIDATION
    let ele = document.getElementById(`${id}_invalid}`)
    ele.addEventListener('invalid', (e) => {
      console.log('e is ', selected, e)
      if (typeof selected === 'undefined' || selected !== null) setInvalid(true)
    })
    //ON COMPONENT EXIT - REMOVE EVENT HANDLER
    return () => {
      ele.removeEventListener('invalid', () => {
        setInvalid(false)
      })
    }
    // eslint-disable-next-line
  }, [])

  //Value property (Allows Single field assignent) - translates to object in for {label:x, value:y}
  useEffect(() => {
    let val
    if (grouped) {
      val = _.findInGroup(options, 'options', (rec) => rec.value === value)
    } else {
      val = options.find((rec) => rec.value === value)
    }
    //console.log('Selected==>', val)
    setSelected(val)
    // eslint-disable-next-line
  }, [value, options])

  //objValue Property (Emulate standard react-select value object)
  useEffect(() => {
    if (objValue) {
      setSelected(objValue)
    }
    // eslint-disable-next-line
  }, [objValue])

  //STYLING SAME AS MDB INPUT COMPONENTS
  const customStyles = {
    valueContainer: (provided, state) => ({
      ...provided,
      backgroundColor: 'aliceblue',
    }),
    dropdownIndicator: (provided, state) => ({
      ...provided,
      backgroundColor: 'aliceblue',
    }),
  }

  const handleChange = (opt, i) => {
    setSelected(opt)
    //Callback function (i is used for nested data in record)
    if (props && props.onChange) props.onChange(opt, i)
  }

  return (
    <Col cols={cols}>
      {label && <label className='tp-label text-uppercase'>{label}</label>}
      <div className='select-wrapper'>
        <ReactSelect
          styles={customStyles}
          value={selected ? selected : ''}
          options={options}
          onChange={(val, i) => handleChange(val, i)}
          isSearchable={true}
          {...o}
        />
        <input
          id={`${id}_invalid}`}
          name={`${id}_invalid}`}
          value={selected ? selected : ''}
          onChange={() => {}}
          tabIndex={-1}
          className={`form-control tp-input w-100 ${invalid ? '' : 'd-none'}`}
          autoComplete='off'
          //value={selected}
          onFocus={() => {
            setInvalid(false)
          }}
          style={{
            position: 'absolute',
            color: 'transparent',
            backgroundColor: 'transparent',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            zIndex: 0,
          }}
          required={true}
        />
      </div>
    </Col>
  )
}

Upvotes: 0

Arunkumar Subramani
Arunkumar Subramani

Reputation: 13

render.js

export const renderSelect = (props) => (
<div>
    <Select
    {...props}
    value={props.input.value}
    onChange={(value) => props.input.onChange(value)}
    onBlur={() => props.input.onBlur(props.input.value)}
    options={props.options}
    key={props.input.value}
    />
    {props.meta.touched && (props.meta.error && <p  style={{ color: "red",fontSize:"12px" }}>{props.meta.error}</p>)}
</div>

);

implementForm.js

<Field 
                name="sex" 
                component={renderSelect} 
                options={Status}
                isClearable={true}
                validate={required}
              />

requiredFileMessage.js

const required = value => value ? undefined : 'Required'

Upvotes: 0

Laura
Laura

Reputation: 8630

On this point there's an issue opened on GitHub.

I see two different approaches:

  1. the "lazy" one, where you change the border colour by adding a specific className. Example here.
  2. As you want to custom the original select I would recommend to embed your customSelect in a separate file. Then you can pass a props isValid and use it to change your borderColor.

class CustomSelect extends React.Component {
  render() {
    const {
      isValid
    } = this.props
    
    const customStyles = {
      control: (base, state) => ({
        ...base,
        // state.isFocused can display different borderColor if you need it
        borderColor: state.isFocused ?
          '#ddd' : isValid ?
          '#ddd' : 'red',
        // overwrittes hover style
        '&:hover': {
          borderColor: state.isFocused ?
            '#ddd' : isValid ?
            '#ddd' : 'red'
        }
      })
    }
    return <Select styles={ customStyles } {...this.props}/>
  }
}

Upvotes: 9

Related Questions