Nik
Nik

Reputation: 1709

Validation using Formik with Yup and React-select

I'm working with a react form validation using Yup along with Formik. There is a react-select element in the form which needs to be validated as well. For validation i'm making use of validationSchema of Formik to validate form on value change. I need only value of the select field as a string so cant take the complete object (key-value). The select field is working fine how ever the validation error message is not cleared. The question is how can I validate the select field with existing approach?

Below is the minimal code sample.

import ReactDOM from "react-dom";
import React, { useState } from "react";
import { Grid, TextField, Button } from "@material-ui/core";
import { Formik } from "formik";
import * as Yup from "yup";
import Select from "react-select";
import "./styles.css";

function App() {
  const [selectedYear, setSelectedYear] = useState("");

  const testSchema = Yup.object().shape({
    name: Yup.string().required("Enter Name"),
    year: Yup.string().required("Select Year")
  });

  const initialValues = {
    name: "",
    year: ""
  };

  const handleYearChange = (selectedYear, values) => {
    values.year = selectedYear.value;
    console.log(selectedYear);
    setSelectedYear(selectedYear);
  };

  const yearOptions = [
    { value: "1960", label: "1960" },
    { value: "1961", label: "1961" },
    { value: "1962", label: "1962" },
    { value: "1963", label: "1963" },
    { value: "1964", label: "1964" },
    { value: "1965", label: "1965" }
  ];

  return (
    <Formik validationSchema={testSchema} initialValues={initialValues}>
      {({
        handleChange,
        handleBlur,
        values,
        errors,
        touched,
        handleSubmit,
        setFieldTouched
      }) => {
        return (
          <>
            <Grid container spacing={2}>
              <Grid item md={12} xs={12}>
                <TextField
                  label="Name"
                  name="name"
                  margin="normal"
                  variant="outlined"
                  onChange={handleChange("name")}
                  style={{ width: "100%", zIndex: 0 }}
                  value={values.name}
                  onBlur={() => {
                    console.log("name");
                  }}
                />
                {errors.name}
              </Grid>

              <Grid item md={6} xs={12}>
                <Select
                  placeholder="Year"
                  value={selectedYear}
                  onChange={selectedOption => {
                    handleYearChange(selectedOption);
                    // handleYearChange(selectedOption, values);
                    // values.year = selectedOption.value;
                    console.log("values", values.year);
                    handleChange("year");
                  }}
                  isSearchable={true}
                  options={yearOptions}
                  name="year"
                  isLoading={false}
                  loadingMessage={() => "Fetching year"}
                  noOptionsMessage={() => "Year appears here"}
                />
                {errors.year}
              </Grid>
              <Grid
                item
                md={4}
                style={{ marginTop: "24px", marginBottom: "10px" }}
                xs={12}
              >
                <Button onClick={handleSubmit}>Save</Button>
              </Grid>
            </Grid>
          </>
        );
      }}
    </Formik>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Here is the codesandbox :

Edit throbbing-shadow-6f6yw

PS: I'm new to Reactjs.

Upvotes: 18

Views: 41724

Answers (8)

Pratik Kulshreshth
Pratik Kulshreshth

Reputation: 125

The setFieldValue can be used here. Refer this code sample below:

<Select
  placeholder="Year"
  value={selectedYear}
  onChange={selectedOption => {
    setFieldValue(selectedOption);
  }}
  isSearchable={true}
  options={yearOptions}
  name="year"
  isLoading={false}
  loadingMessage={() => "Fetching year"}
  noOptionsMessage={() => "Year appears here"}
/>

Upvotes: 0

Mohd. Anas Siddiqui
Mohd. Anas Siddiqui

Reputation: 743

Just destructe the setFieldValue from Formik and then set the value in select of option. Here is the example:

<Select
                            withAsterisk
                            label="Select Name"
                            placeholder="Select State"
                            data={['abc', 'xyz', 'asd', 'vbn']}
                            searchable
                            nothingFoundMessage="No State Found..."
                            name="your_fileld_name_here"
                            value={values.state_select}
                            onChange={(value) => **setFieldValue('your_fileld_name_here', value)}**
                            onBlur={handleBlur}
                        />

Upvotes: 0

Manthan Patel
Manthan Patel

Reputation: 261

You need to fool Formik into considering this as an event. handleChange here is from Formik. This also works for any other input field type such as react-color, or datepicker.

validationSchema = yup.object({
  year_value: yup.object().required('Year value is required.')
})
<Select 
  className=""
  name="year_value"
  id="year_value"
  placeholder='Choose year value'
  value={values.year_value}
  onBlur={handleBlur}
  onChange={selectedOption => {
    let event = {target: {name: 'year_value', value: selectedOption}}
    handleChange(event)
  }}
  onBlur={() => {
    handleBlur({target: {name: 'year_value'}});
  }}
  options={yearOptions}
/>

Upvotes: 11

Sama Gyani
Sama Gyani

Reputation: 1

In case someone is still looking for an asnwer. This will help.

Initially create a custom component using Select component.

import Select from "react-select";

export default function CustomSelect({ onChange, options, value, name, className = "" }) {
    const defaultValue = (options, value) => {
        return options ? options.find((option) => option.value === value) : "";
    };

    return (
        <div>
            <Select
                className={className}
                name={name}
                value={defaultValue(options, value)}
                onChange={(value) => {
                    onChange(value);
                }}
                options={options}
            />
        </div>
    );
}

Import the custom component and use as follows. Make sure to use setFieldTouched and setFieldValue from formik props, to set and validate the form.

<CustomSelect
   name="name"
   value={"value"}
   options={options}
   onChange={(option) => {
   setFieldTouched("name", true);
   setFieldValue("name", option.value);
   }}
/>

Upvotes: 0

Krishna Jangid
Krishna Jangid

Reputation: 5410

I solve my problem by using following code.

import this at the top

import Select from 'react-select';
import { Formik, Form, Field } from 'formik';

now write this code at jsx render part

<Formik
      initialValues={initialUserAddData}
      validationSchema={addUserValidationSchema}
      onSubmit={handleAddUser}
    >
      {({ errors }) => (
        <Form className="add-edit-user-form">
          <Field name="department">
            {({ field, form }) => (
               <Select
                 className="select-wrap"
                 classNamePrefix="select-box"
                 options={department}
                 onChange={(selectedOption) =>
                     form.setFieldValue(
                       'department',
                       selectedOption.value,
                      )
                    }
               />
            )}
         </Field>
         {errors.department && (
           <span className="error">{errors.department}</span>
         )}
       </Form>
      )}
 </Formik>

its only the example for use react-select with formik and update the value in formik validation you can also use useFormik hook for the same but this is the different way

Upvotes: 2

Harsh Suthar
Harsh Suthar

Reputation: 19

I would use Formik select instead of react select like this:

    const initialValues = {
        name: "",
        year: ""
    };

    const testSchema = Yup.object().shape({
      name: Yup.string().required("Enter Name"),
      year: Yup.string().required("Select Year")
    });

    <Field as="select" name="year" id="year">
    <option value="" label="">
    Select Your Year{" "}
    </option>
    {yearOptions.map(item => 
       <option value={item.value} label={item.label}>{item.value}</option>
   )} 
    </Field>

use map on options array to make options

Upvotes: 1

kablamus
kablamus

Reputation: 931

In case somebody is looking for solutions to this in the future, here's an alternative approach to get the selected value as a string from react-select -- and still have Yup perform the validation on the select input:

Using the helpers function from useField(), you can set the value, touched, and error state of a "Field". useField() is helpful any time you're working with elements that aren't inputs, like react-select.

function FormikSelect(...props) {
  const [field, meta, helpers] = useField(name="mySelectInput"); // can pass 'props' into useField also, if 'props' contains a name attribute
  const { setValue, setTouched, setError } = helpers;

  const setFieldProps = (selectedOption) => {
      setValue(selectedOption.value) 
      setTouched(true)
      setError(undefined)
  }

  return (
        <Select onChange={selectedOption => setFieldProps(selectedOption)} />
  );
};

Upvotes: 3

Jap Mul
Jap Mul

Reputation: 18759

Change

handleChange("year")

To

handleChange("year")(selectedOption.value);

Currently the year field in the Formik value isn't updated. The handleChange() function returns a new function that can be called with a value to update the Formik state.

Easiest way to spot these things is by outputting the Formik props with the following code:

<pre>{JSON.stringify(props, null, 2)}</pre>

See this sandbox for an example. In the sandbox I have completely removed the need for the custom year state. I'd recommend using only the Formik state to manipulate the values. Using only Formik state you will probably have to extract only the year part when saving, because react-select uses the complete object by default.

Upvotes: 16

Related Questions