Skip to content

Commit

Permalink
🪟🔧 Ui components: Improve rendering performance (#6295)
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Reuter committed May 2, 2023
1 parent 1b0d61e commit 1bc7784
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 15 deletions.
3 changes: 3 additions & 0 deletions airbyte-webapp/src/components/forms/Form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe(`${Form.name}`, () => {
userEvent.type(screen.getByLabelText("First Name"), "John");
userEvent.type(screen.getByLabelText("Last Name"), "Doe");
const submitButton = screen.getAllByRole("button").filter((button) => button.getAttribute("type") === "submit")[0];
await waitFor(() => expect(submitButton).toBeEnabled());
userEvent.click(submitButton);

await waitFor(() => expect(mockOnSubmit).toHaveBeenCalledWith({ firstName: "John", lastName: "Doe" }));
Expand All @@ -65,6 +66,7 @@ describe(`${Form.name}`, () => {
userEvent.type(screen.getByLabelText("First Name"), "John");
userEvent.type(screen.getByLabelText("Last Name"), "Doe");
const submitButton = screen.getAllByRole("button").filter((button) => button.getAttribute("type") === "submit")[0];
await waitFor(() => expect(submitButton).toBeEnabled());
userEvent.click(submitButton);

await waitFor(() => expect(mockOnSuccess).toHaveBeenCalledTimes(1));
Expand All @@ -89,6 +91,7 @@ describe(`${Form.name}`, () => {
userEvent.type(screen.getByLabelText("First Name"), "John");
userEvent.type(screen.getByLabelText("Last Name"), "Doe");
const submitButton = screen.getAllByRole("button").filter((button) => button.getAttribute("type") === "submit")[0];
await waitFor(() => expect(submitButton).toBeEnabled());
userEvent.click(submitButton);

await waitFor(() => expect(mockOnError).toHaveBeenCalledTimes(1));
Expand Down
9 changes: 7 additions & 2 deletions airbyte-webapp/src/components/forms/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { ReactNode } from "react";
import { useForm, FormProvider, DeepPartial } from "react-hook-form";
import { useForm, FormProvider, DeepPartial, useFormState } from "react-hook-form";
import { SchemaOf } from "yup";

import { FormChangeTracker } from "components/common/FormChangeTracker";
Expand All @@ -18,6 +18,11 @@ interface FormProps<T extends FormValues> {
trackDirtyChanges?: boolean;
}

const HookFormDirtyTracker = () => {
const { isDirty } = useFormState();
return <FormChangeTracker changed={isDirty} />;
};

export const Form = <T extends FormValues>({
children,
onSubmit,
Expand Down Expand Up @@ -45,7 +50,7 @@ export const Form = <T extends FormValues>({

return (
<FormProvider {...methods}>
{trackDirtyChanges && <FormChangeTracker changed={methods.formState.isDirty} />}
{trackDirtyChanges && <HookFormDirtyTracker />}
<form onSubmit={methods.handleSubmit((values) => processSubmission(values))}>{children}</form>
</FormProvider>
);
Expand Down
10 changes: 6 additions & 4 deletions airbyte-webapp/src/components/forms/FormControl.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import classNames from "classnames";
import uniqueId from "lodash/uniqueId";
import { HTMLInputTypeAttribute, ReactNode, useState } from "react";
import { FieldError, Path, useFormContext } from "react-hook-form";
import React from "react";
import { FieldError, Path, get, useFormState } from "react-hook-form";
import { useIntl } from "react-intl";

import { DatePickerProps } from "components/ui/DatePicker/DatePicker";
Expand Down Expand Up @@ -73,14 +74,15 @@ export interface SelectControlProps<T extends FormValues>
}

export const FormControl = <T extends FormValues>({ label, labelTooltip, description, ...props }: ControlProps<T>) => {
const { formState, getFieldState } = useFormContext<T>();
const { error } = getFieldState(props.name, formState); // Subscribe to receive reactive updates
// only retrieve new form state if form state of current field has changed
const { errors } = useFormState<T>({ name: props.name });
const error = get(errors, props.name);
const [controlId] = useState(`input-control-${uniqueId()}`);

// Properties to pass to the underlying control
const controlProps = {
...props,
hasError: Boolean(error),
hasError: error,
controlId,
};

Expand Down
16 changes: 7 additions & 9 deletions airbyte-webapp/src/components/forms/FormSubmissionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFormContext } from "react-hook-form";
import { useFormContext, useFormState } from "react-hook-form";
import { FormattedMessage } from "react-intl";

import { Button } from "components/ui/Button";
Expand All @@ -13,19 +13,17 @@ export const FormSubmissionButtons: React.FC<FormSubmissionButtonsProps> = ({
submitKey = "form.submit",
cancelKey = "form.cancel",
}) => {
const { formState, reset } = useFormContext();
// get isDirty and isSubmitting from useFormState to avoid re-rendering of whole form if they change
// reset is a stable function so it's fine to get it from useFormContext
const { reset } = useFormContext();
const { isDirty, isSubmitting } = useFormState();

return (
<FlexContainer justifyContent="flex-end">
<Button
type="button"
variant="secondary"
disabled={formState.isSubmitting || !formState.isDirty}
onClick={() => reset()}
>
<Button type="button" variant="secondary" disabled={isSubmitting || !isDirty} onClick={() => reset()}>
<FormattedMessage id={cancelKey} />
</Button>
<Button type="submit" disabled={!formState.isDirty} isLoading={formState.isSubmitting}>
<Button type="submit" disabled={!isDirty} isLoading={isSubmitting}>
<FormattedMessage id={submitKey} />
</Button>
</FlexContainer>
Expand Down

0 comments on commit 1bc7784

Please sign in to comment.