Skip to content

Instantly share code, notes, and snippets.

@hubgit
Last active December 29, 2023 03:41
Show Gist options
  • Save hubgit/e394e9be07d95cd5e774989178139ae8 to your computer and use it in GitHub Desktop.
Save hubgit/e394e9be07d95cd5e774989178139ae8 to your computer and use it in GitHub Desktop.
Use react-select with Formik
const options = [
{ value: 'foo', label: 'Foo' },
{ value: 'bar', label: 'Bar' },
]
return <Field name={'example'} component={SelectField} options={options} />
import { FieldProps } from 'formik'
import React from 'react'
import Select, { Option, ReactSelectProps } from 'react-select'
export const SelectField: React.SFC<ReactSelectProps & FieldProps> = ({
options,
field,
form,
}) => (
<Select
options={options}
name={field.name}
value={options ? options.find(option => option.value === field.value) : ''}
onChange={(option: Option) => form.setFieldValue(field.name, option.value)}
onBlur={field.onBlur}
/>
)
@ivan-sakoman
Copy link

I have issue here because onChange function require two types... this is the error: Generic type 'ValueType' requires 2 type argument(s).

i can set secound type to boolean but that makes me another problem, can anybody help me with this problem?

@db306
Copy link

db306 commented Jul 11, 2021

Updated above with couple typing and import fixes

import {FieldProps} from "formik";
import React from "react";
import Select, {OptionsType, ValueType} from "react-select";

interface Option {
    label: string;
    value: string;
}

interface FormikSelectProps extends FieldProps {
    options: OptionsType<Option>;
    isMulti?: boolean;
}

export const FormikSelect =
    ({
         field,
         form,
         options,
         isMulti = false,
     }: FormikSelectProps) => {
        const onChange = (option: ValueType<Option | Option[], boolean>) => {
            form.setFieldValue(
                field.name,
                isMulti
                    ? (option as Option[]).map((item: Option) => item.value)
                    : (option as Option).value
            );
        };

        const getValue = () => {
            if (options) {
                return isMulti
                    ? options.filter(option => field.value.indexOf(option.value) >= 0)
                    : options.find(option => option.value === field.value);
            } else {
                return isMulti ? [] : ("" as any);
            }
        };

        return (
            <Select
                name={field.name}
                value={getValue()}
                onChange={onChange}
                options={options}
                isMulti={isMulti}
            />
        );
    };

@FreTimmerman
Copy link

@db306

isMulti
    ? (option as Option[]).map((item: Option) => item.value)
    : (option as Option).value

instead of casting, you would better have a typeguard to let TS know the difference between option and option[]:

option instanceof Array
    ? option.map((item) => item.value)
    : option.value,

@hdsand
Copy link

hdsand commented Aug 17, 2021

This is not working for me: onBlur={field.onBlur}

The issue is that inside of Formik#handleBlur is the following code:

    var _a = e.target,
        name = _a.name,

It's assuming that the input that gets blurred has a name attribute which is the same as the name that formik is using to reference the field. But since the input attribute here is internal to react-select, it has a generic name. So after I blur, formik.touched is {"react-select-3-input":true}

It works if I do this instead:

       onBlur: function() {
            formik.setFieldTouched(field.name);
        }

Am I doing something wrong here? Is this onBlur approach working for other people?

thanks, it works!

@johnhailu
Copy link

johnhailu commented Sep 7, 2021

I hope it will help someone
Usage

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

<SelectInput name="courseAssignmentId" label="Course Assignment">
    {options.map((option) => (
    <option key={option.value} value={option.value}>
        {option.label}
    </option>
    ))}
</SelectInput>`

my implementation with bootstrap 5

/* eslint-disable react/prop-types */
import React from "react";

import { useField } from "formik";
import Select from "react-select";

function SelectInput({ label, ...props }) {
  const [field, meta, { setValue, setTouched }] = useField(props);
  const options = props.children.map((option) => ({
    value: option.props.value,
    label: option.props.children,
  }));

  const onChange = ({ value }) => {
    setValue(value);
  };

  return (
    <div className="mb-3">
      <label htmlFor={props.id || props.name} className="form-label">
        {label}
      </label>
      <Select
        defaultValue={options.find((option) => option.value === field.value)}
        options={options}
        onChange={onChange}
        onBlur={setTouched}
      />
      {meta.touched && meta.error ? (
        <div className="form-text text-danger">{meta.error}</div>
      ) : null}
    </div>
  );
}
export default SelectInput;

@Matt-Hill-83
Copy link

This is great. thx!

@jimryanzulueta
Copy link

jimryanzulueta commented Sep 27, 2021

Did react-select just change its type definitions? Definitely not seeing OptionsType and ValueType

@asgeo1
Copy link

asgeo1 commented Oct 13, 2021

Did react-select just change its type definitions? Definitely not seeing OptionsType and ValueType

Yes, in v5 the types are different: https://react-select.com/upgrade

OptionsType => Options
ValueType => OnChangeValue

@ocobble
Copy link

ocobble commented Jan 7, 2022

Here's my implementation of an async multi select component used with Formik. This gist was really helpful for me in creating this, but I didn't see any other posts with both async and multi select so I'll add it here.

import { useField, FieldProps } from "formik";
import React from "react";
import AsyncSelect from "react-select/async"

const getOptions = () => {
     return [{value: "1", label: "Test one"}, {value: "2", label: "Test two"}];
  };

  const promiseOptions = () =>
    new Promise((resolve) => {
        timeout(resolve(getOptions()),
        1000);
    });

const asyncMultiSelect = ({
  label,
  ...props
}) => {

  const [field, meta, helpers] = useField(props);

  const { setValue } = helpers;

  const onChange = (option) => {
    setValue(
       (option).map((item) => item.value)
     );
  };

  return (
    <div>
      <label>{label}</label>

      <AsyncSelect
        defaultOptions
        loadOptions={promiseOptions}
        name={field.name}
        onChange={onChange}
        isMulti
      />
      
    </div>
  );
};

export default asyncMultiSelect;

Here's the code in the file with the Formik form:

<Field as={asyncMultiSelect} label="Make a selection" name="mySelect" />

@ken-muturi
Copy link

ken-muturi commented Apr 16, 2023

Updated above with couple typing and import fixes

import {FieldProps} from "formik";
import React from "react";
import Select, {OptionsType, ValueType} from "react-select";

interface Option {
    label: string;
    value: string;
}

interface FormikSelectProps extends FieldProps {
    options: OptionsType<Option>;
    isMulti?: boolean;
}

export const FormikSelect =
    ({
         field,
         form,
         options,
         isMulti = false,
     }: FormikSelectProps) => {
        const onChange = (option: ValueType<Option | Option[], boolean>) => {
            form.setFieldValue(
                field.name,
                isMulti
                    ? (option as Option[]).map((item: Option) => item.value)
                    : (option as Option).value
            );
        };

        const getValue = () => {
            if (options) {
                return isMulti
                    ? options.filter(option => field.value.indexOf(option.value) >= 0)
                    : options.find(option => option.value === field.value);
            } else {
                return isMulti ? [] : ("" as any);
            }
        };

        return (
            <Select
                name={field.name}
                value={getValue()}
                onChange={onChange}
                options={options}
                isMulti={isMulti}
            />
        );
    };

I am having issues with my form using this custom component. The values are not added to formik field values. To explain in detail, I have a select which change, determines the values to pre-select on my custom react-select input (note values here not options). This also cascades this down to a 3rd input. These is a cascading inputs seem to be getting the values, however, the this values are on in the field value object. Please note that because this is a dynamic input am not able to use initialValues object. So i somewhat use the formik props to get the selected values and cascade down. see screenshots below

image

image

image

@alexluongfm
Copy link

React select (single select) that uses more of the formik hooks to reduce props passing and keep everything as simple as possible. Must be under the <Formik>/<Form> code to ensure hooks all work properly.

import React from 'react';
import Select, { Options } from 'react-select';
import { useField } from 'formik';

type Props = {
  selectOptions: Options[],
  formikFieldName: string,
  placeholder?: string,
};

/**
 * React Select but hooked into Formik
 * @returns {JSX.Element}
 * @constructor
 */
const FormikSelect = ({
  selectOptions,
  formikFieldName,
  placeholder,
}: Props) => {
  // eslint-disable-next-line no-unused-vars
  const [field, _, helpers] = useField(formikFieldName);
  const { setValue } = helpers;

  return (
    <Select
      defaultValue={selectOptions.find(
        (option) => option.value === field.value
      )}
      options={selectOptions}
      placeholder={placeholder}
      onBlur={field.onBlur}
      onChange={(option) => setValue(option.value)}
    />
  );
};

FormikSelect.defaultProps = {
  placeholder: '',
};

export default FormikSelect;

Example usage

        <FormikSelect
          selectOptions={[
            { value: 'foo', label: 'Foo' },
            { value: 'bar', label: 'Bar' },
          ]}
          formikFieldName="formikInitVals.shippingMethods"
          placeholder="Shipping Methods"
        />

Versions

formik: 2.4
react: 18.2

@rever96
Copy link

rever96 commented Aug 11, 2023

@alexluongfm with your versions still gives Generic type 'Options' requires 1 type argument(s). error on line 6

"react-select": "^5.7.4",

@IlyaZha
Copy link

IlyaZha commented Sep 22, 2023

My Solution for react-select@^5.7.4:

export default function MultipleSelect({ fieldName, placeholder, ...props }) {
  const [field, meta, { setValue }] = useField(fieldName);
  const options = props.children.map((option) => ({
    value: option.props.value,
    label: option.props.children,
  }));

  const onChange = (selectedOptions: MultiValue<any>) => {
    setValue(selectedOptions);
  }

  return (<>
    <Select
      isMulti={true}
      defaultValue={options.find((option) => option.value === field.value)}
      placeholder={placeholder}
      onChange={onChange}
      options={options}
      onBlur={field.onBlur}
    />
  </>);
}

Form of select should be wrapped with <Formik></Formik>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment