Search code examples
javascriptformsreact-final-form

How to disable submit button in React Final Form when FormSpy is used?


I am working on a template from sharetribe and I had to modify a date availability filter, but I got stuck when I wanted to condition the submit button to disable when nothing is selected from the dates pannel. I tried to add the props submitting, pristine, invalid and use them for this, but it didn't work. Here is the code :

const FilterFormComponent = props => {
  const { liveEdit, onChange, onSubmit, onCancel, onClear, ...rest } = props;

  if (liveEdit && !onChange) {
    throw new Error('FilterForm: if liveEdit is true you need to provide onChange function');
  }

  if (!liveEdit && !(onCancel && onClear && onSubmit)) {
    throw new Error(
      'FilterForm: if liveEdit is false you need to provide onCancel, onClear, and onSubmit functions'
    );
  }

  const handleChange = formState => {
    if (formState.dirty) {
      onChange(formState.values);
    }
  };

  const formCallbacks = liveEdit ? { onSubmit: () => null } : { onSubmit, onCancel, onClear };
  return (
    <FinalForm
      {...rest}
      {...formCallbacks}
      mutators={{ ...arrayMutators }}
      render={formRenderProps => {
        const {
          id,
          form,
          handleSubmit,
          onClear,
          onCancel,
          style,
          paddingClasses,
          intl,
          children,
          submitting
        } = formRenderProps;

        const handleCancel = () => {
          // reset the final form to initialValues
          form.reset();
          onCancel();
        };

        const clear = intl.formatMessage({ id: 'FilterForm.clear' });
        const cancel = intl.formatMessage({ id: 'FilterForm.cancel' });
        const submit = intl.formatMessage({ id: 'FilterForm.submit' });

        const classes = classNames(css.root);

        const spy =
          liveEdit || onChange ? (
            <FormSpy onChange={handleChange} subscription={{ values: true, dirty: true }} />
          ) : null;

          const handleButtonsAvailability = spy ? false : true;

        const buttons = !liveEdit ? (
          <div className={css.buttonsWrapper}>
            <button className={css.clearButton} type="button" onClick={onClear}>
              {clear}
            </button>
            {/* <button className={css.cancelButton} type="button" onClick={handleCancel}>
              {cancel}
            </button> */}
            <button className={css.submitButton} disabled={handleButtonsAvailability} type="submit">
              {submit}           //this is the submit button I want to disable
            </button>
          </div>
        ) : null;

        return (
          <Form
            id={id}
            className={classes}
            onSubmit={handleSubmit}
            tabIndex="0"
            style={{ ...style }}
          >
            <div className={classNames(paddingClasses || css.contentWrapper)}>{children}</div>
            {spy}
            {buttons}
          </Form>
        );
      }}
    />
  );
};

FilterFormComponent.defaultProps = {
  liveEdit: false,
  style: null,
  onCancel: null,
  onChange: null,
  onClear: null,
  onSubmit: null,
};

FilterFormComponent.propTypes = {
  liveEdit: bool,
  onCancel: func,
  onChange: func,
  onClear: func,
  onSubmit: func,
  style: object,
  children: node.isRequired,

  // form injectIntl
  intl: intlShape.isRequired,
};

const FilterForm = injectIntl(FilterFormComponent);


Solution

  • You need to add field validation to FilterForms component (which is using React Final Form component). Since fields are passed in through props.children, you need to add the validate function to correct field (that handles the date picker).

    So, Fields have optional validate property, which works somewhat like this

    validate={value => (value ? undefined : "Required")}
    

    So, the steps to accomplish this are:

    1. Add validator function for the correct Field.
    2. Extract "invalid" value from formRenderProps.
    3. Disable submit button if the "invalid" prop is true.

    The last step needs something like this:

    <button className={classNames(css.submitButton, {[css.submitButtonInvalid]: invalid })} type="submit" disabled={invalid}>
      {submit}
    </button>
    

    In the above code, I assumed that there's CSS Modules class: css.submitButtonInvalid that changes the color of the button.

    Note 1: there are some existing validators in src/util/validators.js

    Note 2: You can play with the FilterForm.example.js by opening the /styleguide/c/FilterForm/FilterFormExample page on your local environment.