Validation using Formik with Yup and React-select
Asked Answered
M

7

18

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.

Manure answered 21/8, 2019 at 14:38 Comment(0)
S
16

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.

Strophic answered 21/8, 2019 at 15:1 Comment(4)
That did the trick, however if i use handleChange("year")(selectedOption.value); it does not show the selected value on react-select box but reflects in values object. However if i do handleChange("year")(selectedOption); then it reflects the selected option on react-select and the values object seems to have nested object. Is there any way where the values object has only string value and not the nested object?Manure
The react-select package requires the complete object by default. The writer wrote a separate package to enable passing only the value part, check it out: github.com/jossmac/react-select-simple-valueStrophic
@jagsler, thank you very much for the desicion because it helped me to resolve the same issue.Kamikamikaze
i don't know why but this method throw my an error saying Cannot read property 'persist' of undefinedKatharina
P
11

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}
/>

Peraza answered 30/9, 2020 at 12:29 Comment(0)
S
3

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)} />
  );
};
Subdivision answered 17/6, 2020 at 0:22 Comment(1)
One feedback I have would be to not to group these in the onChange and to at least break out the setTouched method call and attach it to the onBlur prop as that's how touched events are generally triggered.Shellacking
D
2

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

Danish answered 22/9, 2021 at 5:42 Comment(0)
N
1

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

Nikolaus answered 16/7, 2021 at 18:9 Comment(2)
"use map on options array to make options" perhaps add that to illustrate use of thatSupernaturalism
Thanks! Actually i am a new contributor to stackoverflow, will update my answerNikolaus
J
0

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);
   }}
/>
Juetta answered 20/1, 2022 at 12:18 Comment(0)
R
0

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}
                        />
Raki answered 13/6 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.