Skip to content

Commit

Permalink
refactor(Form): refactor to make more performant
Browse files Browse the repository at this point in the history
  • Loading branch information
ribeirojose committed Apr 28, 2024
1 parent 1b39355 commit 8f93804
Showing 1 changed file with 125 additions and 34 deletions.
159 changes: 125 additions & 34 deletions src/components/ui/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ import {
import { InfoCircledIcon } from "@radix-ui/react-icons";
import { cn } from "#/lib/utils";
import { Label } from "#/components/ui/Label";
import { useRailsApp } from "../RailsApp/context";
import { Tooltip } from ".";
import { Tooltip } from "#/components/ui";
import { useRailsApp } from "#/components/RailsApp";

// Create separate contexts for form field state and updater
const FormFieldStateContext = React.createContext<
FormFieldContextValue | undefined
>(undefined);
const FormFieldUpdaterContext = React.createContext<
React.Dispatch<React.SetStateAction<FormFieldContextValue>> | undefined
>(undefined);

type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
Expand Down Expand Up @@ -52,70 +60,153 @@ const Form = ({
>
{children}
{csrfToken && (
<input type="hidden" name="authenticity_token" value={csrfToken} />
<input
type="hidden"
name="authenticity_token"
value={csrfToken}
data-testid="csrf-token"
/>
)}
</form>
</FormProvider>
);
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
);

// Create a provider component for form field context
export const FormFieldProvider = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
children,
...props
}: React.PropsWithChildren<ControllerProps<TFieldValues, TName>>) => {
const [fieldState, setFieldState] = React.useState<FormFieldContextValue>({
name: props.name,
});

return (
<FormFieldStateContext.Provider value={fieldState}>
<FormFieldUpdaterContext.Provider value={setFieldState}>
{children}
</FormFieldUpdaterContext.Provider>
</FormFieldStateContext.Provider>
);
};

const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<FormFieldContext.Provider value={{ name: props.name }}>
<FormFieldProvider {...props}>
<Controller {...props} />
</FormFieldContext.Provider>
</FormFieldProvider>
);

const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();

const fieldState = getFieldState(fieldContext.name, formState);

if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
// Use hooks to access form field state and updater
export const useFormFieldState = () => {
const fieldState = React.useContext(FormFieldStateContext);
if (typeof fieldState === "undefined") {
throw new Error(
"useFormFieldState must be used within a FormFieldProvider"
);
}
return fieldState;
};

const { id } = itemContext;

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
export const useFormFieldUpdater = () => {
const setFieldState = React.useContext(FormFieldUpdaterContext);
if (typeof setFieldState === "undefined") {
throw new Error(
"useFormFieldUpdater must be used within a FormFieldProvider"
);
}
return setFieldState;
};

// Create separate contexts for form item state and updater
const FormItemStateContext = React.createContext<
FormItemContextValue | undefined
>(undefined);
const FormItemUpdaterContext = React.createContext<
React.Dispatch<React.SetStateAction<FormItemContextValue>> | undefined
>(undefined);

type FormItemContextValue = {
id: string;
};

const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
);
// Create a provider component for form item context
export const FormItemProvider = ({
children,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
const [itemState, setItemState] = React.useState<FormItemContextValue>({
id: React.useId(),
});

return (
<FormItemStateContext.Provider value={itemState}>
<FormItemUpdaterContext.Provider value={setItemState}>
<div {...props}>{children}</div>
</FormItemUpdaterContext.Provider>
</FormItemStateContext.Provider>
);
};

const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<FormItemContext.Provider value={{ id: React.useId() }}>
<FormItemProvider>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
</FormItemProvider>
));
FormItem.displayName = "FormItem";

// Use hooks to access form item state and updater
const useFormItemState = () => {
const itemState = React.useContext(FormItemStateContext);
if (typeof itemState === "undefined") {
throw new Error("useFormItemState must be used within a FormItemProvider");
}
return itemState;
};

// const useFormItemUpdater = () => {
// const setItemState = React.useContext(FormItemUpdaterContext);
// if (typeof setItemState === "undefined") {
// throw new Error(
// "useFormItemUpdater must be used within a FormItemProvider"
// );
// }
// return setItemState;
// };

const useFormField = () => {
const fieldState = useFormFieldState();
const itemState = useFormItemState();
const { getFieldState, formState } = useFormContext();

const fieldStateFromForm = getFieldState(fieldState.name, formState);

if (!fieldState) {
throw new Error("useFormField should be used within <FormField>");
}

const { id } = itemState;

return {
id,
name: fieldState.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldStateFromForm,
};
};

const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & {
Expand Down

0 comments on commit 8f93804

Please sign in to comment.