import {
FieldTitle,
OptionalResourceContextProvider,
SourceContextProvider,
composeSyncValidators,
isRequired,
useApplyInputDefaultValues,
useFormGroupContext,
useFormGroups,
useGetValidationErrorMessage,
useSourceContext,
sanitizeInputRestProps,
ArrayInputContext,
} from "ra-core";
import type { InputProps, SourceContextValue } from "ra-core";
import * as React from "react";
import { useEffect } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { Skeleton } from "../ui/skeleton";
import { InputHelperText } from "@/components/admin/input-helper-text";
import { FormError, FormField } from "@/components/admin/form";
/**
* Creates a list of sub-forms for editing arrays of data embedded inside a record.
*
* Use `` when you need to edit array fields like order items, tags, or any
* repeatable embedded data. Requires a form iterator child (typically ``)
* to render and manage individual array items.
*
* @see {@link https://marmelab.com/shadcn-admin-kit/docs/arrayinput/ ArrayInput documentation}
*
* @example
* import {
* Edit,
* SimpleForm,
* TextInput,
* NumberInput,
* ArrayInput,
* SimpleFormIterator,
* } from '@/components/admin';
*
* const OrderEdit = () => (
*
*
*
*
*
*
*
*
*
*
*
*
*
* );
*/
export const ArrayInput = (props: ArrayInputProps) => {
const {
className,
defaultValue = [],
label,
isPending,
children,
helperText,
resource: resourceFromProps,
source: arraySource,
validate,
...rest
} = props;
const formGroupName = useFormGroupContext();
const formGroups = useFormGroups();
const parentSourceContext = useSourceContext();
const finalSource = parentSourceContext.getSource(arraySource);
const sanitizedValidate = Array.isArray(validate)
? composeSyncValidators(validate)
: validate;
const getValidationErrorMessage = useGetValidationErrorMessage();
const { getValues } = useFormContext();
const fieldProps = useFieldArray({
name: finalSource,
rules: {
validate: async (value) => {
if (!sanitizedValidate) return true;
const error = await sanitizedValidate(value, getValues(), props);
if (!error) return true;
return getValidationErrorMessage(error);
},
},
});
useEffect(() => {
if (formGroups && formGroupName != null) {
formGroups.registerField(finalSource, formGroupName);
}
return () => {
if (formGroups && formGroupName != null) {
formGroups.unregisterField(finalSource, formGroupName);
}
};
}, [finalSource, formGroups, formGroupName]);
useApplyInputDefaultValues({
inputProps: { ...props, defaultValue },
isArrayInput: true,
fieldArrayInputControl: fieldProps,
});
// The SourceContext will be read by children of ArrayInput to compute their composed source and label
//
// => SourceContext is "orders"
// => SourceContext is "orders.0"
// => final source for this input will be "orders.0.date"
//
//
//
const sourceContext = React.useMemo(
() => ({
// source is the source of the ArrayInput child
getSource: (source: string) => {
if (!source) {
// SimpleFormIterator calls getSource('') to get the arraySource
return parentSourceContext.getSource(arraySource);
}
// We want to support nesting and composition with other inputs (e.g. TranslatableInputs, ReferenceOneInput, etc),
// we must also take into account the parent SourceContext
//
// => SourceContext is "orders"
// => SourceContext is "orders.0"
// => final source for this input will be "orders.0.date"
// => SourceContext is "orders.0.items"
// => SourceContext is "orders.0.items.0"
// => final source for this input will be "orders.0.items.0.reference"
//
//
//
//
return parentSourceContext.getSource(`${arraySource}.${source}`);
},
// if Array source is items, and child source is name, .0.name => resources.orders.fields.items.name
getLabel: (source: string) =>
parentSourceContext.getLabel(`${arraySource}.${source}`),
}),
[parentSourceContext, arraySource],
);
if (isPending) {
return ;
}
return (
{children}
);
};
export interface ArrayInputProps
extends Omit {
className?: string;
children: React.ReactNode;
isFetching?: boolean;
isLoading?: boolean;
isPending?: boolean;
}