Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formik & yup form validation not working as expected with VirtualizedSelect

I created a form with formik in order to have form validations. I have used the components Formik, Form, Field form formik and configured them:

    import { Formik, Form, Field } from "formik";
    import { object, string } from "yup";
    import isEmpty from "lodash/isEmpty";
    import FormikSelectInput from "../common/FormikSelectInput";

    class App extends Component {
      render() {
        const options = this.props.categories.map(c => {
          return { label: c.name, value: c.name };
        });

        return (
          <Formik
            validationSchema={object().shape({
              category: string().required("Category is required.")
            })}
            initialValues={this.props.obj}
            onSubmit={(values, actions) => {
              console.log(values);
            }}
            render={({ errors, dirty, isSubmitting, setFieldValue }) => (
              <Form>
                <Field
                  name="category"
                  label="Categories"
                  value={this.props.obj.category.name}
                  options={options}
                  component={FormikSelectInput}
                />
                <button
                  type="submit"
                  className="btn btn-default"
                  disabled={isSubmitting || !isEmpty(errors) || !dirty}
                >
                  Save
                </button>
              </Form>
            )}
          />
        );
      }
    }

    //Prop Types validation
    App.propTypes = {
      obj: PropTypes.object.isRequired,
      categories: PropTypes.array.isRequired,
      actions: PropTypes.object.isRequired
    };
    const getElementByID = (items, id) => {
  let res = items.filter(l => l.id === id);
  return res.length ? res[0] : null; //since filter returns an array we need to check for res.length
};
    //Redux connect
    const mapStateToProps = ({ objects, categories }, ownProps) => {
      let obj = {
        id: "",
        name: "",
        category: { id: "", name: "" }
      };
      return {
        obj: getElementByID(objects, ownProps.match.params.id) || obj,
        categories: categories
      };
    };

    export default connect(
      mapStateToProps,
      {...}
    )(App);

And I have a custom component 'FormikSelectInput':

import React, { Component } from "react";
import classnames from "classnames";
import VirtualizedSelect from "react-virtualized-select";
import "react-select/dist/react-select.css";
import "react-virtualized/styles.css";
import "react-virtualized-select/styles.css";

const InputFeedback = ({ children }) => (
  <span className="text-danger">{children}</span>
);

const Label = ({ error, children, ...props }) => {
  return <label {...props}>{children}</label>;
};

class FormikSelectInput extends Component {
  constructor(props) {
    super(props);
    this.state = { selectValue: this.props.value };
  }

  render() {
    const {
      field: { name, ...field }, // { name, value, onChange, onBlur }
      form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
      className,
      label,
      ...props
    } = this.props;

    const error = errors[name];
    const touch = touched[name];
    const classes = classnames(
      "form-group",
      {
        "animated shake error": !!error
      },
      className
    );

    console.log("props", props);
    return (
      <div className={classes}>
        <Label htmlFor={name} error={error}>
          {label}
        </Label>
<VirtualizedSelect
          name={name}
          id={name}
          className="form-control"
          {...field}
          {...props}
          onChange={(selectValue) => this.setState(() => {
            this.props.form.setFieldValue('category',selectValue)
            return { selectValue } 
          })}
          value={this.state.selectValue}
        /> 
        {touch && error && <InputFeedback>{error}</InputFeedback>}
      </div>
    );
  }
}

export default FormikSelectInput;

My component is working and I am able to select an option, but why formik together with 'yup' validation showing me an error when I empty the select field.

When I clear my select field I get an ERROR - 'category must be a string type, but the final value was: null. If "null" is intended as an empty value be sure to mark the schema as .nullable()'

enter image description here

My code is based on the this example.

like image 665
Kamran Avatar asked Jul 07 '18 11:07

Kamran


People also ask

What is Formik used for?

Formik helps you to write the three most annoying parts of building a form: Getting values in and out of form state. Validation and error messages. Handling form submission.

Which is better Formik or React hook form?

If I want to bootstrap a project with controlled components as fast as possible, then Formik is a great choice. However, it really does not scale well with complex form. React Hook Form was born to solve the performance problem, but well, making it maintainable with external controlled components is a nightmare.

What is Formik and yup?

Formik is a React and React Native library that helps you create forms in React "without the tears". You can pair Formik with validation libraries like Yup to make the process even simpler. In this tutorial, you'll learn how creating and validating forms can be simpler in React using Formik and Yup.

What is better than Formik?

React Hook Form isolates input components from the others and prevents re-render of the form for a single input. It avoids this unnecessary re-rendering. So it is a great advantage in terms of the performance compared to Formik which updates every change in every input field.


1 Answers

It looks like the field is expecting the string to be required based on your validationSchema.

The error helped point me in the right direction. Here's the docs for Yup .nullable(): https://github.com/jquense/yup#mixednullableisnullable-boolean--true-schema

Try adding .nullable() to the chain of validations.

validationSchema={object().shape({ category: string().required("Category is required.").nullable() })}

Hope this helps. enter image description here

like image 147
mwarger Avatar answered Sep 18 '22 16:09

mwarger