Skip to content
Snippets Groups Projects
Unverified Commit 5cba39ab authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Fix form reinitialization with formik (#26292)

parent 53effa4a
No related branches found
No related tags found
No related merge requests found
Showing
with 77 additions and 35 deletions
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { createMockUser } from "metabase-types/api/mocks";
import UserProfileForm, { UserProfileFormProps } from "./UserProfileForm";
describe("UserProfileForm", () => {
it("should show a success message after form submit", async () => {
const props = getProps({
onSubmit: jest.fn().mockResolvedValue({}),
});
render(<UserProfileForm {...props} />);
userEvent.clear(screen.getByLabelText("First name"));
userEvent.type(screen.getByLabelText("First name"), "New name");
userEvent.click(screen.getByText("Update"));
expect(await screen.findByText("Success")).toBeInTheDocument();
});
});
const getProps = (
opts?: Partial<UserProfileFormProps>,
): UserProfileFormProps => ({
user: createMockUser(),
locales: null,
isSsoUser: false,
onSubmit: jest.fn(),
...opts,
});
......@@ -4,6 +4,7 @@ import type { FormikConfig } from "formik";
import type { AnySchema } from "yup";
import useFormSubmit from "metabase/core/hooks/use-form-submit";
import useFormValidation from "metabase/core/hooks/use-form-validation";
import FormContext from "metabase/core/context/FormContext";
export interface FormProviderProps<T, C> extends FormikConfig<T> {
validationSchema?: AnySchema;
......@@ -17,7 +18,7 @@ function FormProvider<T, C>({
onSubmit,
...props
}: FormProviderProps<T, C>): JSX.Element {
const { handleSubmit } = useFormSubmit({ onSubmit });
const { state, handleSubmit } = useFormSubmit({ onSubmit });
const { initialErrors, handleValidate } = useFormValidation({
initialValues,
validationSchema,
......@@ -25,13 +26,15 @@ function FormProvider<T, C>({
});
return (
<Formik
initialValues={initialValues}
initialErrors={initialErrors}
validate={handleValidate}
onSubmit={handleSubmit}
{...props}
/>
<FormContext.Provider value={state}>
<Formik
initialValues={initialValues}
initialErrors={initialErrors}
validate={handleValidate}
onSubmit={handleSubmit}
{...props}
/>
</FormContext.Provider>
);
}
......
import React, { forwardRef, Ref } from "react";
import { t } from "ttag";
import Button, { ButtonProps } from "metabase/core/components/Button";
import { FormStatus } from "metabase/core/hooks/use-form-state";
import useFormSubmitButton from "metabase/core/hooks/use-form-submit-button";
import { FormStatus } from "metabase/core/context/FormContext";
export interface FormSubmitButtonProps extends Omit<ButtonProps, "children"> {
title?: string;
......
import { createContext } from "react";
export type FormStatus = "idle" | "pending" | "fulfilled" | "rejected";
export interface FormState {
status: FormStatus;
message?: string;
}
const FormContext = createContext<FormState>({
status: "idle",
});
export default FormContext;
export { default } from "./FormContext";
export type { FormState, FormStatus } from "./FormContext";
export { default } from "./use-form-context";
export type { FormState, FormStatus } from "metabase/core/context/FormContext";
import { useContext } from "react";
import FormContext, { FormState } from "metabase/core/context/FormContext";
const useFormContext = (): FormState => {
return useContext(FormContext);
};
export default useFormContext;
import { useLayoutEffect, useState } from "react";
import { useFormikContext } from "formik";
import { t } from "ttag";
import useFormState from "metabase/core/hooks/use-form-state";
import useFormContext from "metabase/core/hooks/use-form-context";
const useFormErrorMessage = (): string | undefined => {
const { values, errors } = useFormikContext();
const { status, message } = useFormState();
const { status, message } = useFormContext();
const [isVisible, setIsVisible] = useState(false);
const hasErrors = Object.keys(errors).length > 0;
......
export { default } from "./use-form-state";
export type { FormState, FormStatus } from "./types";
import { useFormikContext } from "formik";
import { FormState } from "./types";
const DEFAULT_STATE: FormState = {
status: "idle",
};
const useFormState = (): FormState => {
const { status } = useFormikContext();
return status ?? DEFAULT_STATE;
};
export default useFormState;
import { useEffect, useLayoutEffect, useState } from "react";
import { useFormikContext } from "formik";
import useFormState, { FormStatus } from "metabase/core/hooks/use-form-state";
import useFormContext, {
FormStatus,
} from "metabase/core/hooks/use-form-context";
const STATUS_TIMEOUT = 5000;
......@@ -17,7 +19,7 @@ const useFormSubmitButton = ({
isDisabled = false,
}: UseFormSubmitButtonProps): UseFormSubmitButtonResult => {
const { isValid, isSubmitting } = useFormikContext();
const { status } = useFormState();
const { status } = useFormContext();
const isRecent = useIsRecent(status, STATUS_TIMEOUT);
return {
......
import { useCallback } from "react";
import { useCallback, useState } from "react";
import type { FormikHelpers } from "formik";
import { FormState } from "metabase/core/context/FormContext";
import { FormError } from "./types";
export interface UseFormSubmitProps<T> {
......@@ -7,30 +8,31 @@ export interface UseFormSubmitProps<T> {
}
export interface UseFormSubmitResult<T> {
state: FormState;
handleSubmit: (values: T, helpers: FormikHelpers<T>) => void;
}
const useFormSubmit = <T>({
onSubmit,
}: UseFormSubmitProps<T>): UseFormSubmitResult<T> => {
const [state, setState] = useState<FormState>({ status: "idle" });
const handleSubmit = useCallback(
async (data: T, helpers: FormikHelpers<T>) => {
try {
helpers.setStatus({ status: "pending" });
setState({ status: "pending" });
await onSubmit(data, helpers);
helpers.setStatus({ status: "fulfilled" });
setState({ status: "fulfilled" });
} catch (error) {
helpers.setErrors(getFormErrors(error));
helpers.setStatus({
status: "rejected",
message: getFormMessage(error),
});
setState({ status: "rejected", message: getFormMessage(error) });
}
},
[onSubmit],
);
return {
state,
handleSubmit,
};
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment