user310988
user310988

Reputation:

How can I show a redux-form warning as a result of async-validation?

Redux-forms supports validation errors and warnings.

An error displays a message and prevents the form from being submitted, where as a warning just shows a message.

Redux-forms also support async validation.

I made the mistake of assuming that async validation errors and warnings would be supported, but this isn't the case.

Unfortunately warnings are not officially possible with async validation.

It would currently take considerable effort to move away from using redux-forms, so I'm trying to find a workaround that will suffice.

One solution would be to manually add warnings to a form. If that's possible then the async validation could be performed mostly as normal, but with setting the warnings at the end, rather than providing the expected error object.

But I've looked at the documentation and there doesn't seem to be a way to manually add warnings, or errors for that matter.

You pass validation functions either to a specific field, or the form as a whole, and these validator functions are called as needed by redux-forms.

So it appears that errors and warnings can't be set directly but maybe we can manually trigger re-validation of a specific field?

No, apparently this is also not currently possible.

So to summarize:

Any suggestions, insight, or corrections are very welcome.
I'll be very happy if I'm wrong on any of those summary points!

If I can't find a solution then I'll look for an alternative library, and I'll start the arduous task of moving away from redux-forms.

This has certainly been a good reminder about the folly of assumptions on my part.

Upvotes: 9

Views: 3302

Answers (2)

ughitsaaron
ughitsaaron

Reputation: 527

The async validation callback for a form passes in dispatch as a second argument. That would allow you to trigger an action to add a custom warning to the global Redux state object. If you don't throw any errors then the form will still be considered valid.

For example, if you wanted to warn the user about some given input but wanted to allow them to submit their data anyway, you could call dispatch with some custom action that adds a warning to a global warnings array.

import { addWarning } from '../actions/warning';
import { db } from '../db';

export const asyncValidate = async (values, dispatch) => {
  const someNonUniqueValueAlreadyExists = await db.findOne('someProperty', values.someValue);

  if (someNonUniqueValueAlreadyExists) {
    // just dispatch the action to add a warning to global state
    // don't throw any error here
    dispatch(addWarning('Is the user sure they want someValue here for someProperty?'));
  }
};

Upvotes: 0

Greg K
Greg K

Reputation: 11120

I went with Dennie's recommendation and use reducer.plugin() in my root reducer to listen for redux form's async validation completion action @@redux-form/STOP_ASYNC_VALIDATION and (rightly or wrongly) change it from an error to a warning by injecting the action.payload into syncWarnings. Redux-form then passes this as a meta.warning prop to the field.

Reducer code:

import { reducer as formReducer } from 'redux-form';

...

const errorToWarning = (state, action) => {
  /* eslint-disable no-unused-vars, no-case-declarations */
  switch (action.type) {
    case "@@redux-form/STOP_ASYNC_VALIDATION":
      const { asyncErrors, ...noErrors } = state;
      const syncWarnings = action.payload || undefined;
      return { ...noErrors, syncWarnings };
    default:
      return state;
  }
};

const rootReducer = combineReducers({
  form: formReducer.plugin({
    FirstForm: errorToWarning,
    AnotherForm: errorToWarning
  }),
  // other reducers
});

export default rootReducer;

Component:

const MyTextField = ({ meta: { touched, error, warning }, ...props }) => {
  let cssClass = "";
  let errorText = "";

  if (touched) {
    cssClass = warning ? "warning" : cssClass;
    cssClass = error ? "error" : cssClass;

    errorText = warning || errorText;
    errorText = error || errorText;
  }

  return (
    <TextField
      className={cssClass}
      hintText={props.hintText || ""}
      {...props}
      errorText={errorText}
      warning={warning}
    />
  );
};

I am using Material UI (that's where TextField comes from, not shown in imports).

See redux-form docs for more info on reducer.plugin().

Upvotes: 6

Related Questions