Search code examples
reactjsmaterial-uireact-selectformikyup

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.


Solution

  • 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.