Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Last active February 18, 2024 09:43
Show Gist options
  • Save donaldpipowitch/4f3989edb2aadd9e44c2856c65e90b2e to your computer and use it in GitHub Desktop.
Save donaldpipowitch/4f3989edb2aadd9e44c2856c65e90b2e 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, useRef } from 'react';
import { useFormikContext } from 'formik';
import { usePrevious } from '../../hooks/use-previous';
function usePrevious<T>(value: T) {
const ref = useRef<T>(value);
useEffect(() => {
ref.current = value;
});
return ref.current;
}
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]);
}
@Hainesy
Copy link

Hainesy commented Jul 7, 2020

Can you post the implementation of usePrevious? Also your useGlobalErrorHandler function syntax is broken.

@donaldpipowitch
Copy link
Author

Can you post the implementation of usePrevious? Also your useGlobalErrorHandler function syntax is broken.

Updated. 👋

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment