pankod/refine

View on GitHub
packages/react-hook-form/src/useStepsForm/index.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { useEffect, useState } from "react";
import { FieldValues, Path } from "react-hook-form";
import { BaseRecord, HttpError } from "@refinedev/core";
import get from "lodash/get";

import { useForm, UseFormProps, UseFormReturnType } from "../useForm";

export type UseStepsFormReturnType<
  TQueryFnData extends BaseRecord = BaseRecord,
  TError extends HttpError = HttpError,
  TVariables extends FieldValues = FieldValues,
  TContext extends object = {},
  TData extends BaseRecord = TQueryFnData,
  TResponse extends BaseRecord = TData,
  TResponseError extends HttpError = TError,
> = UseFormReturnType<
  TQueryFnData,
  TError,
  TVariables,
  TContext,
  TData,
  TResponse,
  TResponseError
> & {
  steps: {
    currentStep: number;
    gotoStep: (step: number) => void;
  };
};

export type UseStepsFormProps<
  TQueryFnData extends BaseRecord = BaseRecord,
  TError extends HttpError = HttpError,
  TVariables extends FieldValues = FieldValues,
  TContext extends object = {},
  TData extends BaseRecord = TQueryFnData,
  TResponse extends BaseRecord = TData,
  TResponseError extends HttpError = TError,
> = UseFormProps<
  TQueryFnData,
  TError,
  TVariables,
  TContext,
  TData,
  TResponse,
  TResponseError
> & {
  /**
     * @description Configuration object for the steps.
     * `defaultStep`: Allows you to set the initial step.
     * 
     * `isBackValidate`: Whether to validation the current step when going back.
     * @type `{
      defaultStep?: number;
      isBackValidate?: boolean;
      }`
     * @default `defaultStep = 0` `isBackValidate = false`
     */
  stepsProps?: {
    defaultStep?: number;
    isBackValidate?: boolean;
  };
};

export const useStepsForm = <
  TQueryFnData extends BaseRecord = BaseRecord,
  TError extends HttpError = HttpError,
  TVariables extends FieldValues = FieldValues,
  TContext extends object = {},
  TData extends BaseRecord = TQueryFnData,
  TResponse extends BaseRecord = TData,
  TResponseError extends HttpError = TError,
>({
  stepsProps,
  ...rest
}: UseStepsFormProps<
  TQueryFnData,
  TError,
  TVariables,
  TContext,
  TData,
  TResponse,
  TResponseError
> = {}): UseStepsFormReturnType<
  TQueryFnData,
  TError,
  TVariables,
  TContext,
  TData,
  TResponse,
  TResponseError
> => {
  const { defaultStep = 0, isBackValidate = false } = stepsProps ?? {};
  const [current, setCurrent] = useState(defaultStep);

  const useHookFormResult = useForm<
    TQueryFnData,
    TError,
    TVariables,
    TContext,
    TData,
    TResponse,
    TResponseError
  >({
    ...rest,
  });

  const {
    trigger,
    getValues,
    setValue,
    formState: { dirtyFields },
    refineCore: { queryResult },
  } = useHookFormResult;

  useEffect(() => {
    const data = queryResult?.data?.data;
    if (!data) return;

    const registeredFields = Object.keys(getValues());

    console.log({
      dirtyFields,
      registeredFields,
      data,
    });

    Object.entries(data).forEach(([key, value]) => {
      const name = key as Path<TVariables>;

      if (registeredFields.includes(name)) {
        if (!get(dirtyFields, name)) {
          setValue(name, value);
        }
      }
    });
  }, [queryResult?.data, current, setValue, getValues]);

  const go = (step: number) => {
    let targetStep = step;

    if (step < 0) {
      targetStep = 0;
    }

    setCurrent(targetStep);
  };

  const gotoStep = async (step: number) => {
    if (step === current) {
      return;
    }

    if (step < current && !isBackValidate) {
      go(step);
      return;
    }

    const isValid = await trigger();
    if (isValid) {
      go(step);
    }
  };

  return {
    ...useHookFormResult,
    steps: {
      currentStep: current,
      gotoStep,
    },
  };
};