aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/flip/flip.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/atoms/flip/flip.tsx')
0 files changed, 0 insertions, 0 deletions
a id='n38' href='#n38'>38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
import { useCallback, useState, type FormEvent } from 'react';
import type { DataValidator, Maybe, Nullable } from '../../../../types';

export type FormSubmitMessages = {
  /**
   * The message to use on error.
   */
  error: string;
  /**
   * The message to use on success.
   */
  success: string;
};

export type FormSubmitValidation<T> = {
  /**
   * A callback to handle submit validation.
   */
  validator: DataValidator<T>;
  /**
   * The messages to use on failure or success.
   */
  messages: Partial<FormSubmitMessages>;
};

export type FormSubmitHandler<T> = (
  data: T
) => Maybe<FormSubmitValidation<T>> | Promise<Maybe<FormSubmitValidation<T>>>;

export type FormSubmitStatus = 'IDLE' | 'PENDING' | 'FAILED' | 'SUCCEEDED';

export type FormHandlers<T extends Record<string, unknown>> = {
  /**
   * A callback function to handle submit failure.
   */
  onFailure: () => void;
  /**
   * A callback function to handle submit success.
   */
  onSuccess: () => void;
  /**
   * A callback function to handle submit.
   */
  submit: FormSubmitHandler<T>;
};

export type UseFormSubmitReturn = {
  /**
   * The message to use on submit failure or success.
   */
  messages: Nullable<Partial<FormSubmitMessages>>;
  /**
   * A method to handle form submit.
   *
   * @param {FormEvent<HTMLFormElement>} e - The event.
   * @returns {Promise<void>}
   */
  submit: (e: FormEvent<HTMLFormElement>) => Promise<void>;
  /**
   * The submit status.
   */
  submitStatus: FormSubmitStatus;
};

/**
 * React hook to handle form submit.
 *
 * @template {object} T - The object keys should match the fields name.
 * @param {T} data - The form values.
 * @param {Partial<FormHandlers<T>>} handlers - The submit handlers.
 * @returns {UseFormSubmitReturn} A submit method, the status and messages.
 */
export const useFormSubmit = <T extends Record<string, unknown>>(
  data: T,
  handlers?: Partial<FormHandlers<T>>
): UseFormSubmitReturn => {
  const { onFailure, onSuccess, submit: submitHandler } = handlers ?? {};
  const [messages, setMessages] =
    useState<Nullable<Partial<FormSubmitMessages>>>(null);
  const [submitStatus, setSubmitStatus] = useState<FormSubmitStatus>('IDLE');

  const handleFailure = useCallback(() => {
    setSubmitStatus('FAILED');
    if (onFailure) onFailure();
  }, [onFailure]);

  const handleSuccess = useCallback(() => {
    setSubmitStatus('SUCCEEDED');
    if (onSuccess) onSuccess();
  }, [onSuccess]);

  const handleSubmit = useCallback(async () => {
    const submitResult = submitHandler ? await submitHandler(data) : undefined;

    if (!submitResult) {
      handleSuccess();
      return;
    }

    setMessages(submitResult.messages);

    const isSuccess = submitResult.validator(data);

    setSubmitStatus(isSuccess ? 'SUCCEEDED' : 'FAILED');

    if (isSuccess) handleSuccess();
    else handleFailure();
  }, [data, handleFailure, handleSuccess, submitHandler]);

  const submit = useCallback(
    async (e: FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      setMessages(null);
      setSubmitStatus('PENDING');

      return handleSubmit();
    },
    [handleSubmit]
  );

  return { messages, submit, submitStatus };
};