import { ElementType } from 'react'; import classNames from 'classnames'; import Field, { FieldProps } from '../Field/Field'; import { FieldNameContext } from '../Field/FieldNameContext'; import useStandardFormInput from '../Field/useStandardField'; import { FormDefaults } from '../FormDefaults'; import { ValidationFunction } from '../Validation/ValidationFunction'; export type FieldArrayProps< TForm extends object, TProp extends keyof TForm > = TForm[TProp] extends Array | undefined | null ? { /** Name of the field, used on submission. If using codegen this must be the provided dto. */ name: TProp; /** Label of the field. */ label?: string; /** Whether the field should be disabled. */ disabled?: boolean; /** Function to validate the value. */ validate?: | ValidationFunction | ValidationFunction[]; children: ( formBuilder: ArrayFormBuilderProp ) => JSX.Element; } : never; export type ArrayFormBuilderProp | undefined | null> = TValue extends Array | undefined | null ? TForm extends object ? { Field: < TProp extends keyof TForm, TRenderComponent extends ElementType >( props: FieldProps ) => JSX.Element; // assumes this is never null - thought he final component may not render FieldArray: ( props: FieldArrayProps ) => JSX.Element; } : never : never; /** * An array of fields that allows the user to add multiple instances of the same field. * * Includes "Add Item" and "Remove Item" buttons to allow the user to speicify the number of fields. */ export default function FieldArray< TForm extends object, TProp extends keyof TForm >({ name, label, validate, disabled, children, ...rest }: FieldArrayProps) { const [input] = useStandardFormInput({ name: String(name), validate: validate, disabled: disabled, }); const values: any[] = !input.value ? [] : Array.isArray(input.value) ? input.value : []; return (
{label}
{values.map((value, index) => { const itemName = `${input.name}[${index}]`; return (
{children({ Field: Field, FieldArray: FieldArray, } as any)}
); })}
); function addItem() { if (disabled) { return; } input.onChange([...values, {}] as any); } function removeItem(item: any) { if (disabled) { return; } // assumes anything from the server has an 'id' value sent if (!item.id) { input.onChange(values.filter((x) => x !== item) as any); return; } const mapped = values.map((x) => x !== item ? x : Object.assign({}, x, { isDeleted: true }) ); input.onChange(mapped as any); } }