2
votes

I am relatively new to React and especially new to both react-select and Formik. I have a form that has an Input component and three Select components. The input as well as the two selects that use isMulti for multiple selected options at once clear just fine when I use just a basic reset button, but the single select component does not. If I check what the values are they are empty, but the UI does not reflect this change. I have tried:

utilizing resetForm(), setting it to the initialValues as well as an empty object.

using onReset and implicitly calling resetForm from there.

using a few different variations of setFieldValue

I thought it might be the way my initialValues were set up, but at this point I am just going in circles and hoping a more seasoned eye can pick up on this.

(PS- the example in the docs shows you how to use React-Select with Formik with a reset button, but it does not give an example of a non-multi select.)

The single select has a name of 'paid', and I have include the object which I believe is correct using a value and a label property

simplified sandbox. desired behavior: clicking 'reset' will reset the option to the initialValues and show the placeholder text in the UI.

https://codesandbox.io/s/peh1q

const costOptions = [
   { value: 'true', label: 'Paid' },
   { value: 'false', label: 'Free' },
];

Resources.propTypes = {
  initialValues: shape({
    category: array,
    q: string,
    languages: array,
    paid: string,
  }),
};

Resources.defaultProps = {
  initialValues: {
    category: [],
    q: '',
    languages: [],
    paid: '',
  },
};

       <Formik
          enableReinitialize
          initialValues={initialValues}
          onSubmit={(values, actions) => {
            handleSubmit(values, actions);
            actions.setSubmitting(true);
          }}
        >
          {({ isSubmitting }) => (
            <Form>
              <Field
                data-testid={RESOURCE_SEARCH}
                disabled={isSubmitting}
                type="search"
                name="q"
                label="Search Keywords"
                component={Input}
              />
              <div className={styles.formContainer}>
                <div className={styles.selectColumn}>
                  <Field
                    isDisabled={isSubmitting}
                    isMulti
                    placeholder="Start typing a category..."
                    label="By Category"
                    name="category"
                    options={allCategories}
                    component={Select}
                  />
                </div>

                <div className={styles.selectColumn}>
                  <Field
                    isDisabled={isSubmitting}
                    placeholder="Resource cost..."
                    label="By Cost"
                    name="paid"
                    options={costOptions}
                    component={Select}
                  />
                </div>

                <div className={styles.selectColumn}>
                  <Field
                    isDisabled={isSubmitting}
                    placeholder="Start typing a language..."
                    isMulti
                    label="By Language(s)"
                    name="languages"
                    options={allLanguages}
                    component={Select}
                  />
                </div>
              </div>
              <div className={styles.buttonGroup}>
                <Button disabled={isSubmitting} type="submit">
                  Search
                </Button>

                <Button disabled={isSubmitting} type="reset">
                  Reset
                </Button>
              </div>
            </Form>
          )}
        </Formik>
2
Are you able to include an example using codesandbox.io or fiddle? That would help in debugging your particular issueEmma
@Emma thanks! I made a simplified version of the problem here: codesandbox.io/s/peh1q desired behavior: clicking 'reset' button will reset the select to the initialValues and show the placeholder text 'resources cost...' in the UI.Manthonyg

2 Answers

3
votes

So nothing I needed to fix was actually in the code I posted (learning point taken) but it was in the codesandbox.

Select component being used in Formik looks like this:

import React from 'react';
import {
  arrayOf,
  bool,
  func,
  number,
  object,
  objectOf,
  oneOfType,
  shape,
  string,
} from 'prop-types';
import { ErrorMessage } from 'formik';
import Alert from 'components/Alert/Alert';
import Label from 'components/Form/Label/Label';
import ThemedReactSelect from './ThemedReactSelect';
import styles from './Select.module.css';

Select.propTypes = {
  field: shape({
    name: string.isRequired,
    value: oneOfType([string.isRequired, arrayOf(string.isRequired).isRequired]),
  }).isRequired,
  form: shape({
    // TODO: Resolve why multiselects can end up with touched: { key: array }
    // see ThemedReactSelect as well
    // touched: objectOf(bool).isRequired,
    touched: object.isRequired,
    errors: objectOf(string).isRequired,
    setFieldTouched: func.isRequired,
    setFieldValue: func.isRequired,
  }).isRequired,
  hasValidationStyling: bool,
  id: oneOfType([string, number]),
  isLabelHidden: bool,
  isMulti: bool,
  label: string.isRequired,
  options: arrayOf(shape({ label: string.isRequired, value: string.isRequired }).isRequired)
    .isRequired,
};

Select.defaultProps = {
  hasValidationStyling: true,
  id: undefined,
  isLabelHidden: false,
  isMulti: false,
};

export default function Select({
  field: { name, value: fieldValue },
  form: { errors, setFieldTouched, setFieldValue, touched },
  hasValidationStyling,
  id,
  isLabelHidden,
  isMulti,
  label,
  options,
  ...props // disabled, placeholder, etc.
}) {
  /**
   * @description handle changing of non-multi select
   * @param {string} selected
   */
  const onChangeSingle = selected => {
    setFieldValue(name, selected.value);
  };

  /**
   * @description handle changing of multi select
   * @param {string[]} selectedArray
   */
  const onChangeMulti = selectedArray => {
    if (selectedArray) {
      setFieldValue(
        name,
        selectedArray.map(item => item.value),
      );
    } else {
      setFieldValue(name, []);
    }
  };

  /**
   * @description Return the selected value as a string
   * @returns {string}
   */
  const getValueFromSingle = () => {
    return options.find(option => option.value === fieldValue);
  };

  /**
   * @description Return an array of selected values for multi selects
   * @returns {string[]}
   */
  const getValueFromMulti = () => {
    return options.filter(option => fieldValue.includes(option.value));
  };

  const handleBlur = () => {
    setFieldTouched(name);
  };

  const hasErrors = Boolean(errors[name]);

  // handlers and value depend on whether or not select allows for multiple selections.
  const value = isMulti ? getValueFromMulti() : getValueFromSingle();
  const onChangeHandler = isMulti ? onChangeMulti : onChangeSingle;

  return (
    <div className={styles.field}>
      <Label for={name} isHidden={isLabelHidden}>
        {label}
      </Label>

      <div className={styles.selectFeedbackGrouping}>
        <ThemedReactSelect
          {...props}
          hasErrors={hasErrors}
          hasValidationStyling={hasValidationStyling}
          isTouched={touched[name]}
          id={id || name}
          isMulti={isMulti}
          name={name}
          onBlur={handleBlur}
          onChange={onChangeHandler}
          options={options}
          value={value}
        />

        <ErrorMessage
          name={name}
          render={message => {
            return hasErrors ? (
              <Alert className={styles.errorMessage} type="error">
                {message}
              </Alert>
            ) : null;
          }}
        />
      </div>
    </div>
  );
}

Firstly, handle the null value that gets passed in handleOnChange when an input has isClearable={true} and you click the 'X' to clear the select

 const onChangeSingle = selected => {
    setFieldValue(name, selected === null ? '' : selected.value);
  };

Then, give a fallback for the field value (in the ThemedReactSelect above)

<ThemedReactSelect
          {...props}
          hasErrors={hasErrors}
          hasValidationStyling={hasValidationStyling}
          isTouched={touched[name]}
          id={id || name}
          isMulti={isMulti}
          name={name}
          onBlur={handleBlur}
          onChange={onChangeHandler}
          options={options}
          value={value || ''}
        />

and now the single selects work just like the multis when form is reset.

0
votes

Have you tried adding the isClearable prop to the single value dropdown?

        <Formik
          enableReinitialize
          initialValues={initialValues}
          onSubmit={(values, actions) => {
            handleSubmit(values, actions);
            actions.setSubmitting(true);
          }}
        >
          {({ isSubmitting }) => (
            <Form>
              //...Other Formik components

                <div className={styles.selectColumn}>
                  <Field
                    isClearable={true} // <-- added this
                    isDisabled={isSubmitting}
                    placeholder="Resource cost..."
                    label="By Cost"
                    name="paid"
                    options={costOptions}
                    component={Select}
                  />
                </div>

                <div className={styles.selectColumn}>
                  <Field
                    isDisabled={isSubmitting}
                    placeholder="Start typing a language..."
                    isMulti
                    label="By Language(s)"
                    name="languages"
                    options={allLanguages}
                    component={Select}
                  />
                </div>
              </div>
              <div className={styles.buttonGroup}>
                <Button disabled={isSubmitting} type="submit">
                  Search
                </Button>

                <Button disabled={isSubmitting} type="reset">
                  Reset
                </Button>
              </div>
            </Form>
          )}
        </Formik>