Skip to content

Instantly share code, notes, and snippets.

@rbngzlv
Forked from donaldpipowitch/README.md
Created October 14, 2019 06:56
Show Gist options
  • Save rbngzlv/0924b8e04330207c262f951748c3e5ac to your computer and use it in GitHub Desktop.
Save rbngzlv/0924b8e04330207c262f951748c3e5ac to your computer and use it in GitHub Desktop.
Handle server errors in Formik

Whenever the server returns validation errors and we would set them with setFieldError they would be lost if any field would get a change or blur event. But we want to keep these kind of errors until the specific field changes. Additional we want to handle generic server errors (which are not specific to a field, but the whole form).

With these hooks field specific server side errors should be added like this:

const { setStatus } = useFormikContext();

const errors = {};
// adjust serverErrors to your own responses
// in this case they look like this: Array<{ name: string, error: string }>
serverErrors.forEach(({ name, error }) => (errors = setIn(errors, field, error))); 
setStatus(errors);

Global server errors should be added like this:

const { setStatus } = useFormikContext();
setStatus(true);

You should use useGlobalErrorHandler once inside your form and useErrorHandler once for every field. It could look like this:

type Props = {
  name: string;
} & InputHTMLAttributes<HTMLInputElement>;

export const Input: FC<Props> = (props) => {
  const { name, ...inputProps } = props;

  const [field, meta] = useField(name);
  const [showError, serverError] = useErrorHandler(field, meta);

  return (
    <>
      <input
        style={{ borderColor: showError ? 'red' : 'black' }}
        {...field}
        {...inputProps}
      />

      {showError && (serverError || meta.error) && (
        <p>{serverError || meta.error}</p>
      )}
    </>
  );
};
import { useEffect } from 'react';
import {
useFormikContext,
FieldMetaProps,
FieldInputProps,
getIn,
setIn
} from 'formik';
import { usePrevious } from '../../hooks/use-previous';
export function useErrorHandler(
field: FieldInputProps<any>,
meta: FieldMetaProps<any>
) {
const { submitCount, status, setStatus } = useFormikContext();
const prevValue = usePrevious(field.value);
const globalError = status === true;
const serverError: string | undefined = getIn(status, field.name);
// the client side validation errors should be shown, if the field was
// touched or if the user tried to submit the form
const showError = Boolean(
globalError || serverError || (meta.error && (meta.touched || submitCount))
);
// reset server side validation error on change
useEffect(() => {
if (serverError && field.value !== prevValue) {
setStatus(setIn(status, field.name, undefined));
}
}, [serverError, field.value, prevValue, setStatus, status, field.name]);
return [showError, serverError] as const;
}
import { useEffect } from 'react';
import { useFormikContext } from 'formik';
import { usePrevious } from '../../hooks/use-previous';
export function useGlobalErrorHandler(
const { status, setStatus, values } = useFormikContext();
const prevValues = usePrevious(values);
// reset global server error on *any* change
useEffect(() => {
if (status === true && values !== prevValues) {
setStatus(undefined);
}
}, [status, values, prevValues, setStatus]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment