Files
medusa-store/packages/admin-ui/ui/src/utils/nested-form.ts
2023-03-13 14:02:20 +01:00

88 lines
2.7 KiB
TypeScript

import { get } from "lodash"
import { FieldPath, FieldValues, UseFormReturn } from "react-hook-form"
import { Get } from "type-fest"
export type NestedForm<TValues extends FieldValues> = UseFormReturn<{
__nested__: TValues
}> & {
path(this: void): `__nested__`
path<TPath extends FieldPath<TValues>>(
this: void,
p?: TPath
): `__nested__.${TPath}`
get<TObj>(this: void, obj: TObj): Get<TObj, `__nested__`>
get<TPath extends FieldPath<TValues>, TObj>(
this: void,
obj: TObj,
p?: TPath
): Get<TObj, `__nested__.${TPath}`>
}
/**
* Utility function to create a nested form. This is useful when you want to use a reusable form component within a form.
* This is especially useful when you want to use a reusable form component within a form multiple times. For example, an form
* that contains both a billing and a shipping address.
* @example
* const MyForm = () => {
* const form = useForm<{ email: string, shipping_address: AddressPayload, billing_address: AddressPayload }>()
*
* return (
* <div>
* <Input {...form.register("email")} label="email" />
* <AddressForm form={nestedForm(form, "shipping_address")} />
* <AddressForm form={nestedForm(form, "billing_address")} />
* </div>
* )
* }
*
* type AddressFormProps = {
* form: NestedForm<AddressPayload>
* }
*
* const AddressForm = ({ form }: AddressFormProps) => {
* const { register, path } = form
*
* return (
* <div>
* <Input {...register(path("city"))} label="City" /> // path("city") resolves as "shipping_address.city" or "billing_address.city" depending on the second argument passed to nestedForm
* <Input {...register(path("postal_code"))} label="Postal Code" />
* </div>
* )
* }
*/
export function nestedForm<TValues extends FieldValues>(
form: UseFormReturn<TValues> | NestedForm<TValues>
): NestedForm<TValues>
export function nestedForm<
TValues extends FieldValues,
TPath extends FieldPath<TValues>
>(
form: UseFormReturn<TValues> | NestedForm<TValues>,
path: TPath
): NestedForm<Get<TValues, TPath>>
export function nestedForm(
form: UseFormReturn<any> | NestedForm<any>,
path?: string | number
): NestedForm<any> {
return {
...form,
path(field?: string | number) {
const fullPath = path && field ? `${path}.${field}` : path ? path : field
if ("path" in form) {
return form.path(path as any)
}
return (fullPath || "") as any
},
get(obj: any, field?: string | number) {
const fullPath = path && field ? `${path}.${field}` : path ? path : field
if ("get" in form) {
return form.get(path)
}
return fullPath ? get(obj, fullPath) : obj
},
}
}