jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
34.02k stars 2.79k forks source link

When you have a Select with multiple allowed in Typescript the values should accept type string[] #1696

Open Diego-Romero opened 5 years ago

Diego-Romero commented 5 years ago

🐛 Bug report

Current Behavior

React Bootstrap Select multiple showing error in Formik as it is expecting a string but returns array. Currently the code won't compile the way it currently is, Nor it would allow me to select multiple options or get any when the form submits.

Error: Warning: The value prop supplied to must be an array if multiple is true.

Check the render method of FormControl.

I believe that the error is on FormControl.d.ts inside Formik because it only accepts a string as a type

This is the Interface that Formik exposes

export interface FormControlProps {
      innerRef?: React.LegacyRef<this>;
      size?: 'sm' | 'lg';
      plaintext?: boolean;
      readOnly?: boolean;
      disabled?: boolean;
      value?: string; // <-- it should also receive string[] ?
      onChange?: React.FormEventHandler<this>;
      type?: string;
      id?: string;
      isValid?: boolean;
      isInvalid?: boolean;
   }

StackOverflow issue: https://stackoverflow.com/questions/57149598/react-bootstrap-select-multiple-showing-error-in-formik-as-it-is-expecting-a-str

Expected behavior

I should be able to pass an array of string as that is what I'm expecting back

Reproducible example

   import React from 'react';
    import { Container, Row, Col, Card, Form, Button } from 'react-bootstrap';
    import { connect } from 'react-redux';
    import { Formik } from 'formik';
    import styled from 'styled-components';
    import * as Yup from 'yup';

    const ExpertSignUpSchema = Yup.object().shape({
        expertise: Yup.array()
            .of(Yup.string())
            .min(1)
            .required('Required'),
    });

    const ExpertSignUpPage: React.FC = () => (
        <Container fluid>
            <Row>
                <Col md={4} />
                <Col>
                    <div>
                        <Card>
                            <Card.Header as="h5">Expert Registration</Card.Header>
                            <Card.Body>
                                <Formik
                                    initialValues={{
                                        expertise: [''],
                                    }}
                                    validationSchema={ExpertSignUpSchema}
                                    onSubmit={(values, { setSubmitting }) => {
                                        console.log(values);
                                        setSubmitting(false);
                                    }}
                                    render={({
                                        values,
                                        errors,
                                        touched,
                                        handleBlur,
                                        handleChange,
                                        handleSubmit,
                                        isSubmitting,
                                    }) => (
                                        <Form noValidate onSubmit={handleSubmit}>
                                                <Form.Control
                                                    as="select"
                                                    multiple
                                                    name="expertise"
                                                    onChange={handleChange}
                                                    onBlur={handleBlur}
                                                    value={values.expertise} // <--- it should allow an array of strings, currently the code won't compile or won't update the form value as it has multiple in the form control
                                                    isValid={touched.expertise && !errors.expertise}
                                                    isInvalid={!!errors.expertise}
                                                >
                                                    <option value="YOGA">Yoga</option>
                                                    <option value="PERSONAL_TRAINER">Personal Trainer</option>
                                                    <option value="LIFE_COACH">Life Coach</option>
                                                    <option value="NUTRITIONIST">Nutritionist</option>
                                                </Form.Control>
                                            </Form.Group>
                                            <Button
                                                variant="outline-primary"
                                                type="submit"
                                                block={true}
                                                disabled={isSubmitting}
                                            >
                                                Register
                                            </Button>
                                        </Form>
                                    )}
                                />
                            </Card.Body>
                        </Card>
                    </div>
                </Col>
                <Col md={4} />
            </Row>
        </Container>
    );

    export default ExpertSignUpPage;

Suggested solution(s)

Update the interface to accept string[] too?

Your environment

Software Version(s)
Formik "formik": "^1.5.8",
React "react": "^16.8.6",
TypeScript "typescript": "3.5.2",
Browser
npm/Yarn 1.16.0
Operating System MacOS
kieranbop commented 4 years ago

Any update on this?

lmwright6 commented 4 years ago

I have also encountered this as an issue. In Typescript you essentially cannot have a multi-select form field in Formik.

johnrom commented 4 years ago

Formik doesn't have anything to do with this. You can have a multi-select form field in Formik.

https://codesandbox.io/s/blissful-wilson-j0sy4?file=/index.tsx

You can also create your own form fields in Formik, which could apply strong typing to your select box. Unfortunately, it doesn't seem that the library that @Diego-Romero is using maps the values properly.

Docs showing multi select:

https://formik.org/docs/api/field#example

You can do something like:

interface SelectOption<Value> {
  label: string,
  value: Value,
};
const MultiSelect = <Value extends string>(props: {
  name: string,
  options: SelectOption<Value>
}) => {
  const [field] = React.useField<Value[]>({ 
    name: props.name, 
    // values has proper types
    validate: (values) => values.every(value => !!options.find(option => option.value === value)),
  });

  return <select name={props.name} value={field.value} multiple>
    {props.options.map(option => (
      <option key={value} value={value}>{option.label ?? option.value}</option>
    ))}
  </select>;
}
lmwright6 commented 4 years ago

Ah I see thank you! This link for formik migration is helpful too: https://formik.org/docs/migrating-v2

PhilipposVassil commented 1 year ago

I had the same issue and I found a solution to my problem. I didn't use the above way that was suggested but I put a single parameter in my code and the error disappeared!

example code:

problematic code:

<select ... multiple >
   <option ... >
      ....
   </option>
</select>

solution:

<select ... multiple value={ value }>
   <option ... >
      ....
   </option>
</select>

where value is the value you get from yup validation via .test() case

I hope this helps