) : null;
};
export interface SimpleFormIteratorProps extends Partial {
addButton?: ReactElement;
children?: ReactElement | ReactElement[];
className?: string;
readOnly?: boolean;
disabled?: boolean;
disableAdd?: boolean;
disableClear?: boolean;
disableRemove?: boolean | DisableRemoveFunction;
disableReordering?: boolean;
fullWidth?: boolean;
getItemLabel?: boolean | GetItemLabelFunc;
inline?: boolean;
meta?: {
// the type defined in FieldArrayRenderProps says error is boolean, which is wrong.
error?: any;
submitFailed?: boolean;
};
record?: RaRecord;
removeButton?: ReactElement;
reOrderButtons?: ReactElement;
resource?: string;
source?: string;
}
/**
* A single item in a SimpleFormIterator list with controls.
*
* Renders one item from an array with its input fields and action buttons (remove, reorder).
* Usually used internally by SimpleFormIterator but can be customized.
*
* @example
* import { SimpleFormIteratorItem } from '@/components/admin';
*
* // Typically used internally by SimpleFormIterator
*/
export const SimpleFormIteratorItem = React.forwardRef(
(
props: SimpleFormIteratorItemProps,
ref: React.ForwardedRef,
) => {
const {
children,
disabled,
disableReordering,
disableRemove,
getItemLabel,
index,
inline,
record,
removeButton = defaultRemoveItemButton,
reOrderButtons = defaultReOrderButtons,
} = props;
const resource = useResourceContext(props);
if (!resource) {
throw new Error(
"SimpleFormIteratorItem must be used in a ResourceContextProvider or be passed a resource prop.",
);
}
const { total, reOrder, remove } = useSimpleFormIterator();
// Returns a boolean to indicate whether to disable the remove button for certain fields.
// If disableRemove is a function, then call the function with the current record to
// determining if the button should be disabled. Otherwise, use a boolean property that
// enables or disables the button for all of the fields.
const disableRemoveField = (record: RaRecord) => {
if (typeof disableRemove === "boolean") {
return disableRemove;
}
return disableRemove && disableRemove(record);
};
const context = useMemo(
() => ({
index,
total,
reOrder: (newIndex) => reOrder(index, newIndex),
remove: () => remove(index),
}),
[index, total, reOrder, remove],
);
const label =
typeof getItemLabel === "function" ? getItemLabel(index) : getItemLabel;
const parentSourceContext = useSourceContext();
const sourceContext = useMemo(
() => ({
getSource: (source: string) => {
if (!source) {
// source can be empty for scalar values, e.g.
// => SourceContext is "tags"
// => SourceContext is "tags.0"
// => use its parent's getSource so finalSource = "tags.0"
//
//
return parentSourceContext.getSource(`${index}`);
} else {
// Normal input with source, e.g.
// => SourceContext is "orders"
// => SourceContext is "orders.0"
// => use its parent's getSource so finalSource = "orders.0.date"
//
//
return parentSourceContext.getSource(`${index}.${source}`);
}
},
getLabel: (source: string) => {
// => LabelContext is "orders"
// => LabelContext is ALSO "orders"
// => use its parent's getLabel so finalLabel = "orders.date"
//
//
//
// we don't prefix with the index to avoid that translation keys contain it
return parentSourceContext.getLabel(source);
},
}),
[index, parentSourceContext],
);
return (