Use select in Semantic UI with Formik

Viewed 1892

I'm usign Formik to validate some data fields with Semantic UI in React. It works fine with input fields but doesn't work with selectors.

How it works with input fields:

    import { Formik, Form, Field } from 'formik';
    import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
    import * as Yup from 'yup';

    ...

  const initialValues = {
     name: ''
  };
  const requiredErrorMessage = 'This field is required';
  const validationSchema = Yup.object({ name: Yup.string().required(requiredErrorMessage) });
   
  <Formik
  htmlFor="amazing"
  initialValues={initialValues}
  validationSchema={validationSchema}
  onSubmit={values => this.handleSubmit(values)}>
  {({ errors, touched }) => (
    <Form id="amazing">
      <div>
        <CreationContentContainer>
          <Grid>
            <Grid.Column>
              <Label>Company name</Label>
              <Field name="name" as={Input} placeholder="name" /> // here is the input 
              <div>{touched.name && errors.name ? errors.name : null}</div>
            </Grid.Column>
          </Grid>
        </CreationContentContainer>
        <Button type="submit" form="amazing">
          Create company
        </Button>
      </div>
    </Form>
  )}
</Formik>;

However, when the as={Input} is replaced by as={Select}, it doesn't work. The dropdown gets opened when I click on the selector, it shows the options (company1 and company2), I click on one of them but it does not work -> the value shown is still the placeholder value.

import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';

const companyOptions = [ // the select's options
  { text: 'company1', value: 'company1' },
  { text: 'company2', value: 'company2' },
];

<Formik
  htmlFor="amazing"
  initialValues={initialValues}
  validationSchema={validationSchema}
  onSubmit={values => this.handleSubmit(values)}>
  {({ errors, touched }) => (
    <Form id="amazing">
      <div>
        <CreationContentContainer>
          <Grid>
            <Grid.Column>
              <Label>Company name</Label>
              <Field
                name="name"
                as={Select} // here is changed to Select
                options={companyOptions} // the options
                placeholder="name"
              />
              <div>{touched.name && errors.name ? errors.name : null}</div>
            </Grid.Column>
          </Grid>
        </CreationContentContainer>
        <Button type="submit" form="amazing">
          Create company
        </Button>
      </div>
    </Form>
  )}
</Formik>;
2 Answers

The reason is because Formik passes an onChange function into the component. However, the Select (which is just syntactic sugar for dropdown https://react.semantic-ui.com/addons/select/) onChange prop uses a function of shape handleChange = (e: React.SyntheticEvent<HTMLElement>, data: SelectProps) => this.setState({value: data.value}) (https://react.semantic-ui.com/modules/dropdown/#usage-controlled) which is different than the conventional handleChange = (event: React.SyntheticEvent<HTMLElement>) => this.setState({value: event.target.value}) which formik uses.

So an easy solution would be to use the Formik function setFieldValue.

In your code, the implementation would be something like this:

const companyOptions = [ // the select's options
  { text: 'company1', value: 'company1' },
  { text: 'company2', value: 'company2' },
];

<Formik
  htmlFor="amazing"
  initialValues={initialValues}
  validationSchema={validationSchema}
  onSubmit={values => this.handleSubmit(values)}>
  {({ errors, touched, setFieldValue, values }) => {
    const handleChange = (e, { name, value }) => setFieldValue(name, value));
    return (
      <Form id="amazing">
        <div>
          <CreationContentContainer>
            <Grid>
              <Grid.Column>
                <Label>Company name</Label>
                <Field
                  name="name"
                  as={Select} // here is changed to Select
                  options={companyOptions} // the options
                  placeholder="name"
                  onChange={handleChange}
                  value={values.name}
                />
                <div>{touched.name && errors.name ? errors.name : null}</div>
              </Grid.Column>
            </Grid>
          </CreationContentContainer>
          <Button type="submit" form="amazing">
            Create company
          </Button>
        </div>
      </Form>
  )}
}
</Formik>;

Here's a somewhat working version using your code. The fields update (verified with a console.log), but the submit button isn't working ‍♂️: https://codesandbox.io/s/formik-and-semanticui-select-gb7o7

This is a somewhat incomplete solution and does not handle the onBlur and other injected formik functions. It also defeats the purpose of Formik somewhat. A better solution would be to create a HOC that wraps semantic ui components to use Fromik correctly: https://github.com/formium/formik/issues/148.

And lastly, you can skip the Semantic UI Select and use the Semantic UI HTML controlled select: https://react.semantic-ui.com/collections/form/#shorthand-field-control-html. Notice the select vs Select. This will work natively with Formik without the need to pass in onChange and value.

I recently created a binding library for Semantic UI React & Formik.

Link: https://github.com/JT501/formik-semantic-ui-react

You just need to import Select from the library and fill in name & options props.

// Import components
import { Select, SubmitButton } from "formik-semantic-ui-react";

const companyOptions = [ // the select's options
  { text: 'company1', value: 'company1' },
  { text: 'company2', value: 'company2' },
];

<Formik
  htmlFor="amazing"
  initialValues={initialValues}
  validationSchema={validationSchema}
  onSubmit={values => this.handleSubmit(values)}>
  {({ errors, touched }) => (
    <Form id="amazing">
      <div>
        <CreationContentContainer>
          <Grid>
            <Grid.Column>
              <Label>Company name</Label>

              <Select
                 name="name"
                 selectOnBlur={false}
                 clearable
                 placeholder="name"
                 options={companyOptions}
              />

            </Grid.Column>
          </Grid>
        </CreationContentContainer>
        <SubmitButton>
          Create company
        </SubmitButton>
      </div>
    </Form>
  )}
</Formik>;
Related