// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listDogs } from \\"../graphql/queries\\"; import { createOwner, updateDog, updateOwner } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function CreateOwnerForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Dog: undefined, }; const [name, setName] = React.useState(initialValues.name); const [Dog, setDog] = React.useState(initialValues.Dog); const [DogLoading, setDogLoading] = React.useState(false); const [DogRecords, setDogRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setDog(initialValues.Dog); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); const [currentDogValue, setCurrentDogValue] = React.useState(undefined); const DogRef = React.createRef(); const getIDValue = { Dog: (r) => JSON.stringify({ id: r?.id }), }; const DogIdSet = new Set( Array.isArray(Dog) ? Dog.map((r) => getIDValue.Dog?.(r)) : getIDValue.Dog?.(Dog) ); const getDisplayValue = { Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], Dog: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchDogRecords = async (value) => { setDogLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listDogs, variables, }) )?.data?.listDogs?.items; var loaded = result.filter( (item) => !DogIdSet.has(getIDValue.Dog?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setDogRecords(newOptions.slice(0, autocompleteLength)); setDogLoading(false); }; React.useEffect(() => { fetchDogRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { name, Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const owner = await API.graphql({ query: createOwner, variables: { input: { ...modelFields, }, }, }); const promises = []; const dogToLink = modelFields.Dog; if (dogToLink) { promises.push( API.graphql({ query: updateDog, variables: { input: { ...Dog, owner: owner, }, }, }) ); const ownerToUnlink = await dogToLink.owner; if (ownerToUnlink) { promises.push( API.graphql({ query: updateOwner, variables: { input: { ...ownerToUnlink, Dog: undefined, ownerDogId: undefined, }, }, }) ); } } await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateOwnerForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { name: value, Dog, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, Dog: value, }; const result = onChange(modelFields); value = result?.Dog ?? value; } setDog(value); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); }} currentFieldValue={currentDogValue} label={\\"Dog\\"} items={Dog ? [Dog] : []} hasError={errors?.Dog?.hasError} errorMessage={errors?.Dog?.errorMessage} getBadgeText={getDisplayValue.Dog} setFieldValue={(model) => { setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); setCurrentDogValue(model); }} inputFieldRef={DogRef} defaultFieldValue={\\"\\"} > !DogIdSet.has(getIDValue.Dog?.(r)) ).map((r) => ({ id: getIDValue.Dog?.(r), label: getDisplayValue.Dog?.(r), }))} isLoading={DogLoading} onSelect={({ id, label }) => { setCurrentDogValue( DogRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentDogDisplayValue(label); runValidationTasks(\\"Dog\\", label); }} onClear={() => { setCurrentDogDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchDogRecords(value); if (errors.Dog?.hasError) { runValidationTasks(\\"Dog\\", value); } setCurrentDogDisplayValue(value); setCurrentDogValue(undefined); }} onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} errorMessage={errors.Dog?.errorMessage} hasError={errors.Dog?.hasError} ref={DogRef} labelHidden={true} {...getOverrideProps(overrides, \\"Dog\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type CreateOwnerFormInputValues = { name?: string; Dog?: any; }; export declare type CreateOwnerFormValidationValues = { name?: ValidationFunction; Dog?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type CreateOwnerFormOverridesProps = { CreateOwnerFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; Dog?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type CreateOwnerFormProps = React.PropsWithChildren<{ overrides?: CreateOwnerFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onSuccess?: (fields: CreateOwnerFormInputValues) => void; onError?: (fields: CreateOwnerFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onValidate?: CreateOwnerFormValidationValues; } & React.CSSProperties>; export default function CreateOwnerForm(props: CreateOwnerFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { createPost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function MyPostForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { caption: \\"\\", username: \\"\\", post_url: \\"\\", metadata: \\"\\", profile_url: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [caption, setCaption] = React.useState(initialValues.caption); const [username, setUsername] = React.useState(initialValues.username); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setCaption(initialValues.caption); setUsername(initialValues.username); setPost_url(initialValues.post_url); setMetadata(initialValues.metadata); setProfile_url(initialValues.profile_url); setNonModelField(initialValues.nonModelField); setNonModelFieldArray(initialValues.nonModelFieldArray); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { caption: [], username: [], post_url: [{ type: \\"URL\\" }], metadata: [{ type: \\"JSON\\" }], profile_url: [{ type: \\"URL\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( { event.preventDefault(); let modelFields = { caption, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { caption: modelFields.caption, username: modelFields.username, post_url: modelFields.post_url, metadata: modelFields.metadata, profile_url: modelFields.profile_url, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await API.graphql({ query: createPost, variables: { input: { ...modelFieldsToSave, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { caption: value, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} > { let { value } = e.target; if (onChange) { const modelFields = { caption, username: value, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} > { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url: value, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} > { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata: value, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} > { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} > { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} > { let values = items; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type MyPostFormInputValues = { caption?: string; username?: string; post_url?: string; metadata?: string; profile_url?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type MyPostFormValidationValues = { caption?: ValidationFunction; username?: ValidationFunction; post_url?: ValidationFunction; metadata?: ValidationFunction; profile_url?: ValidationFunction; nonModelField?: ValidationFunction; nonModelFieldArray?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type MyPostFormOverridesProps = { MyPostFormGrid?: PrimitiveOverrideProps; caption?: PrimitiveOverrideProps; username?: PrimitiveOverrideProps; post_url?: PrimitiveOverrideProps; metadata?: PrimitiveOverrideProps; profile_url?: PrimitiveOverrideProps; nonModelField?: PrimitiveOverrideProps; nonModelFieldArray?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type MyPostFormProps = React.PropsWithChildren<{ overrides?: MyPostFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onSuccess?: (fields: MyPostFormInputValues) => void; onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listTeams } from \\"../graphql/queries\\"; import { createMember } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", teamID: undefined, Team: undefined, }; const [name, setName] = React.useState(initialValues.name); const [teamID, setTeamID] = React.useState(initialValues.teamID); const [teamIDLoading, setTeamIDLoading] = React.useState(false); const [teamIDRecords, setTeamIDRecords] = React.useState([]); const [Team, setTeam] = React.useState(initialValues.Team); const [TeamLoading, setTeamLoading] = React.useState(false); const [TeamRecords, setTeamRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setTeamID(initialValues.teamID); setCurrentTeamIDValue(undefined); setCurrentTeamIDDisplayValue(\\"\\"); setTeam(initialValues.Team); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = React.useState(\\"\\"); const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); const teamIDRef = React.createRef(); const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(\\"\\"); const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); const TeamRef = React.createRef(); const getIDValue = { Team: (r) => JSON.stringify({ id: r?.id }), }; const TeamIdSet = new Set( Array.isArray(Team) ? Team.map((r) => getIDValue.Team?.(r)) : getIDValue.Team?.(Team) ); const getDisplayValue = { teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Team: (r) => r?.name, }; const validations = { name: [], teamID: [{ type: \\"Required\\" }], Team: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchTeamIDRecords = async (value) => { setTeamIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listTeams, variables, }) )?.data?.listTeams?.items; var loaded = result.filter((item) => teamID !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } setTeamIDRecords(newOptions.slice(0, autocompleteLength)); setTeamIDLoading(false); }; const fetchTeamRecords = async (value) => { setTeamLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listTeams, variables, }) )?.data?.listTeams?.items; var loaded = result.filter( (item) => !TeamIdSet.has(getIDValue.Team?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setTeamRecords(newOptions.slice(0, autocompleteLength)); setTeamLoading(false); }; React.useEffect(() => { fetchTeamIDRecords(\\"\\"); fetchTeamRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { name, teamID, Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: createMember, variables: { input: { ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyMemberForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { name: value, teamID, Team, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, teamID: value, Team, }; const result = onChange(modelFields); value = result?.teamID ?? value; } setTeamID(value); setCurrentTeamIDValue(undefined); }} currentFieldValue={currentTeamIDValue} label={\\"Team id\\"} items={teamID ? [teamID] : []} hasError={errors?.teamID?.hasError} errorMessage={errors?.teamID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.teamID(teamIDRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentTeamIDDisplayValue( value ? getDisplayValue.teamID( teamIDRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentTeamIDValue(value); }} inputFieldRef={teamIDRef} defaultFieldValue={\\"\\"} > arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.teamID?.(r), }))} isLoading={teamIDLoading} onSelect={({ id, label }) => { setCurrentTeamIDValue(id); setCurrentTeamIDDisplayValue(label); runValidationTasks(\\"teamID\\", label); }} onClear={() => { setCurrentTeamIDDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchTeamIDRecords(value); if (errors.teamID?.hasError) { runValidationTasks(\\"teamID\\", value); } setCurrentTeamIDDisplayValue(value); setCurrentTeamIDValue(undefined); }} onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} errorMessage={errors.teamID?.errorMessage} hasError={errors.teamID?.hasError} ref={teamIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"teamID\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, teamID, Team: value, }; const result = onChange(modelFields); value = result?.Team ?? value; } setTeam(value); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); }} currentFieldValue={currentTeamValue} label={\\"Team Label\\"} items={Team ? [Team] : []} hasError={errors?.Team?.hasError} errorMessage={errors?.Team?.errorMessage} getBadgeText={getDisplayValue.Team} setFieldValue={(model) => { setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); setCurrentTeamValue(model); }} inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > !TeamIdSet.has(getIDValue.Team?.(r)) ).map((r) => ({ id: getIDValue.Team?.(r), label: getDisplayValue.Team?.(r), }))} isLoading={TeamLoading} onSelect={({ id, label }) => { setCurrentTeamValue( TeamRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentTeamDisplayValue(label); runValidationTasks(\\"Team\\", label); }} onClear={() => { setCurrentTeamDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchTeamRecords(value); if (errors.Team?.hasError) { runValidationTasks(\\"Team\\", value); } setCurrentTeamDisplayValue(value); setCurrentTeamValue(undefined); }} onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} errorMessage={errors.Team?.errorMessage} hasError={errors.Team?.hasError} ref={TeamRef} labelHidden={true} {...getOverrideProps(overrides, \\"Team\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Team } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type MyMemberFormInputValues = { name?: string; teamID?: string; Team?: Team; }; export declare type MyMemberFormValidationValues = { name?: ValidationFunction; teamID?: ValidationFunction; Team?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type MyMemberFormOverridesProps = { MyMemberFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; teamID?: PrimitiveOverrideProps; Team?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type MyMemberFormProps = React.PropsWithChildren<{ overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onSuccess?: (fields: MyMemberFormInputValues) => void; onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listStudents } from \\"../graphql/queries\\"; import { createSchool, updateSchool } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function SchoolCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Students: [], }; const [name, setName] = React.useState(initialValues.name); const [Students, setStudents] = React.useState(initialValues.Students); const [StudentsLoading, setStudentsLoading] = React.useState(false); const [StudentsRecords, setStudentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setStudents(initialValues.Students); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); setErrors({}); }; const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = React.useState(\\"\\"); const [currentStudentsValue, setCurrentStudentsValue] = React.useState(undefined); const StudentsRef = React.createRef(); const getIDValue = { Students: (r) => JSON.stringify({ id: r?.id }), }; const StudentsIdSet = new Set( Array.isArray(Students) ? Students.map((r) => getIDValue.Students?.(r)) : getIDValue.Students?.(Students) ); const getDisplayValue = { Students: (r) => r?.name, }; const validations = { name: [], Students: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchStudentsRecords = async (value) => { setStudentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listStudents, variables, }) )?.data?.listStudents?.items; var loaded = result.filter( (item) => !StudentsIdSet.has(getIDValue.Students?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setStudentsRecords(newOptions.slice(0, autocompleteLength)); setStudentsLoading(false); }; React.useEffect(() => { fetchStudentsRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { name, Students, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { name: modelFields.name, }; const school = await API.graphql({ query: createSchool, variables: { input: { ...modelFieldsToSave, }, }, }); const promises = []; promises.push( ...Students.reduce((promises, original) => { promises.push( API.graphql({ query: updateSchool, variables: { input: { ...original, schoolID: school.id, }, }, }) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { name: value, Students, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} > { let values = items; if (onChange) { const modelFields = { name, Students: values, }; const result = onChange(modelFields); values = result?.Students ?? values; } setStudents(values); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); }} currentFieldValue={currentStudentsValue} label={\\"Students\\"} items={Students} hasError={errors?.Students?.hasError} errorMessage={errors?.Students?.errorMessage} getBadgeText={getDisplayValue.Students} setFieldValue={(model) => { setCurrentStudentsDisplayValue( model ? getDisplayValue.Students(model) : \\"\\" ); setCurrentStudentsValue(model); }} inputFieldRef={StudentsRef} defaultFieldValue={\\"\\"} > !StudentsIdSet.has(getIDValue.Students?.(r)) ).map((r) => ({ id: getIDValue.Students?.(r), label: getDisplayValue.Students?.(r), }))} isLoading={StudentsLoading} onSelect={({ id, label }) => { setCurrentStudentsValue( StudentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentStudentsDisplayValue(label); runValidationTasks(\\"Students\\", label); }} onClear={() => { setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchStudentsRecords(value); if (errors.Students?.hasError) { runValidationTasks(\\"Students\\", value); } setCurrentStudentsDisplayValue(value); setCurrentStudentsValue(undefined); }} onBlur={() => runValidationTasks(\\"Students\\", currentStudentsDisplayValue) } errorMessage={errors.Students?.errorMessage} hasError={errors.Students?.hasError} ref={StudentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Students\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Student } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type SchoolCreateFormInputValues = { name?: string; Students?: Student[]; }; export declare type SchoolCreateFormValidationValues = { name?: ValidationFunction; Students?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type SchoolCreateFormOverridesProps = { SchoolCreateFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; Students?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type SchoolCreateFormProps = React.PropsWithChildren<{ overrides?: SchoolCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; onSuccess?: (fields: SchoolCreateFormInputValues) => void; onError?: (fields: SchoolCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; onValidate?: SchoolCreateFormValidationValues; } & React.CSSProperties>; export default function SchoolCreateForm(props: SchoolCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listAuthors } from \\"../graphql/queries\\"; import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", primaryAuthor: undefined, }; const [name, setName] = React.useState(initialValues.name); const [primaryAuthor, setPrimaryAuthor] = React.useState( initialValues.primaryAuthor ); const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPrimaryAuthor(initialValues.primaryAuthor); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); setErrors({}); }; const [ currentPrimaryAuthorDisplayValue, setCurrentPrimaryAuthorDisplayValue, ] = React.useState(\\"\\"); const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); const primaryAuthorRef = React.createRef(); const getIDValue = { primaryAuthor: (r) => JSON.stringify({ id: r?.id }), }; const primaryAuthorIdSet = new Set( Array.isArray(primaryAuthor) ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) : getIDValue.primaryAuthor?.(primaryAuthor) ); const getDisplayValue = { primaryAuthor: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], primaryAuthor: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPrimaryAuthorRecords = async (value) => { setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listAuthors, variables, }) )?.data?.listAuthors?.items; var loaded = result.filter( (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); setPrimaryAuthorLoading(false); }; React.useEffect(() => { fetchPrimaryAuthorRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { name, primaryAuthor, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: createBook, variables: { input: { ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { name: value, primaryAuthor, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor: value, }; const result = onChange(modelFields); value = result?.primaryAuthor ?? value; } setPrimaryAuthor(value); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} items={primaryAuthor ? [primaryAuthor] : []} hasError={errors?.primaryAuthor?.hasError} errorMessage={errors?.primaryAuthor?.errorMessage} getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { setCurrentPrimaryAuthorDisplayValue( model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); setCurrentPrimaryAuthorValue(model); }} inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) ) .map((r) => ({ id: getIDValue.primaryAuthor?.(r), label: getDisplayValue.primaryAuthor?.(r), }))} isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( primaryAuthorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryAuthorDisplayValue(label); runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPrimaryAuthorRecords(value); if (errors.primaryAuthor?.hasError) { runValidationTasks(\\"primaryAuthor\\", value); } setCurrentPrimaryAuthorDisplayValue(value); setCurrentPrimaryAuthorValue(undefined); }} onBlur={() => runValidationTasks( \\"primaryAuthor\\", currentPrimaryAuthorDisplayValue ) } errorMessage={errors.primaryAuthor?.errorMessage} hasError={errors.primaryAuthor?.hasError} ref={primaryAuthorRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryAuthor\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Author } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type BookCreateFormInputValues = { name?: string; primaryAuthor?: Author; }; export declare type BookCreateFormValidationValues = { name?: ValidationFunction; primaryAuthor?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type BookCreateFormOverridesProps = { BookCreateFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; primaryAuthor?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type BookCreateFormProps = React.PropsWithChildren<{ overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onSuccess?: (fields: BookCreateFormInputValues) => void; onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listPosts } from \\"../graphql/queries\\"; import { createTag, createTagPost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { label: \\"\\", Posts: [], statuses: [], }; const [label, setLabel] = React.useState(initialValues.label); const [Posts, setPosts] = React.useState(initialValues.Posts); const [PostsLoading, setPostsLoading] = React.useState(false); const [PostsRecords, setPostsRecords] = React.useState([]); const [statuses, setStatuses] = React.useState(initialValues.statuses); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setLabel(initialValues.label); setPosts(initialValues.Posts); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); setStatuses(initialValues.statuses); setCurrentStatusesValue(\\"\\"); setErrors({}); }; const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); const PostsRef = React.createRef(); const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); const statusesRef = React.createRef(); const getIDValue = { Posts: (r) => JSON.stringify({ id: r?.id }), }; const PostsIdSet = new Set( Array.isArray(Posts) ? Posts.map((r) => getIDValue.Posts?.(r)) : getIDValue.Posts?.(Posts) ); const getDisplayValue = { Posts: (r) => r?.title, statuses: (r) => { const enumDisplayValueMap = { PENDING: \\"Pending\\", POSTED: \\"Posted\\", IN_REVIEW: \\"In review\\", }; return enumDisplayValueMap[r]; }, }; const validations = { label: [], Posts: [], statuses: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPostsRecords = async (value) => { setPostsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter( (item) => !PostsIdSet.has(getIDValue.Posts?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPostsRecords(newOptions.slice(0, autocompleteLength)); setPostsLoading(false); }; React.useEffect(() => { fetchPostsRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { label, Posts, statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { label: modelFields.label, statuses: modelFields.statuses, }; const tag = await API.graphql({ query: createTag, variables: { input: { ...modelFieldsToSave, }, }, }); const promises = []; promises.push( ...Posts.reduce((promises, post) => { promises.push( API.graphql({ query: createTagPost, variables: { input: { tag, post, }, }, }) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"TagCreateForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { label: value, Posts, statuses, }; const result = onChange(modelFields); value = result?.label ?? value; } if (errors.label?.hasError) { runValidationTasks(\\"label\\", value); } setLabel(value); }} onBlur={() => runValidationTasks(\\"label\\", label)} errorMessage={errors.label?.errorMessage} hasError={errors.label?.hasError} {...getOverrideProps(overrides, \\"label\\")} > { let values = items; if (onChange) { const modelFields = { label, Posts: values, statuses, }; const result = onChange(modelFields); values = result?.Posts ?? values; } setPosts(values); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); }} currentFieldValue={currentPostsValue} label={\\"Posts\\"} items={Posts} hasError={errors?.Posts?.hasError} errorMessage={errors?.Posts?.errorMessage} getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { setCurrentPostsDisplayValue( model ? getDisplayValue.Posts(model) : \\"\\" ); setCurrentPostsValue(model); }} inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > ({ id: getIDValue.Posts?.(r), label: getDisplayValue.Posts?.(r), }))} isLoading={PostsLoading} onSelect={({ id, label }) => { setCurrentPostsValue( PostsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostsDisplayValue(label); runValidationTasks(\\"Posts\\", label); }} onClear={() => { setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPostsRecords(value); if (errors.Posts?.hasError) { runValidationTasks(\\"Posts\\", value); } setCurrentPostsDisplayValue(value); setCurrentPostsValue(undefined); }} onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} errorMessage={errors.Posts?.errorMessage} hasError={errors.Posts?.hasError} ref={PostsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Posts\\")} > { let values = items; if (onChange) { const modelFields = { label, Posts, statuses: values, }; const result = onChange(modelFields); values = result?.statuses ?? values; } setStatuses(values); setCurrentStatusesValue(\\"\\"); }} currentFieldValue={currentStatusesValue} label={\\"Statuses\\"} items={statuses} hasError={errors?.statuses?.hasError} errorMessage={errors?.statuses?.errorMessage} getBadgeText={getDisplayValue.statuses} setFieldValue={setCurrentStatusesValue} inputFieldRef={statusesRef} defaultFieldValue={\\"\\"} > { let { value } = e.target; if (errors.statuses?.hasError) { runValidationTasks(\\"statuses\\", value); } setCurrentStatusesValue(value); }} onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} errorMessage={errors.statuses?.errorMessage} hasError={errors.statuses?.hasError} ref={statusesRef} labelHidden={true} {...getOverrideProps(overrides, \\"statuses\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type TagCreateFormInputValues = { label?: string; Posts?: Post[]; statuses?: string[]; }; export declare type TagCreateFormValidationValues = { label?: ValidationFunction; Posts?: ValidationFunction; statuses?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; export declare type TagCreateFormOverridesProps = { TagCreateFormGrid?: PrimitiveOverrideProps; label?: PrimitiveOverrideProps; Posts?: PrimitiveOverrideProps; statuses?: PrimitiveOverrideProps; } & EscapeHatchProps; export declare type TagCreateFormProps = React.PropsWithChildren<{ overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onSuccess?: (fields: TagCreateFormInputValues) => void; onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listAuthors, listTitles } from \\"../graphql/queries\\"; import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = {label}; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( {!!items?.length && ( {items.map((value, index) => { return ( { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} { event.stopPropagation(); removeItem(index); }} /> ); })} )} ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( {labelElement} {arraySection} ); } return ( {labelElement} {isEditing && children} {!isEditing ? ( <> {errorMessage && hasError && ( {errorMessage} )} ) : ( {(currentFieldValue || isEditing) && ( )} )} {arraySection} ); } export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", primaryAuthor: undefined, primaryTitle: undefined, }; const [name, setName] = React.useState(initialValues.name); const [primaryAuthor, setPrimaryAuthor] = React.useState( initialValues.primaryAuthor ); const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); const [primaryTitle, setPrimaryTitle] = React.useState( initialValues.primaryTitle ); const [primaryTitleLoading, setPrimaryTitleLoading] = React.useState(false); const [primaryTitleRecords, setPrimaryTitleRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPrimaryAuthor(initialValues.primaryAuthor); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); setPrimaryTitle(initialValues.primaryTitle); setCurrentPrimaryTitleValue(undefined); setCurrentPrimaryTitleDisplayValue(\\"\\"); setErrors({}); }; const [ currentPrimaryAuthorDisplayValue, setCurrentPrimaryAuthorDisplayValue, ] = React.useState(\\"\\"); const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); const primaryAuthorRef = React.createRef(); const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = React.useState(\\"\\"); const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = React.useState(undefined); const primaryTitleRef = React.createRef(); const getIDValue = { primaryAuthor: (r) => JSON.stringify({ id: r?.id }), primaryTitle: (r) => JSON.stringify({ id: r?.id }), }; const primaryAuthorIdSet = new Set( Array.isArray(primaryAuthor) ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) : getIDValue.primaryAuthor?.(primaryAuthor) ); const primaryTitleIdSet = new Set( Array.isArray(primaryTitle) ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) : getIDValue.primaryTitle?.(primaryTitle) ); const getDisplayValue = { primaryAuthor: (r) => r?.name, primaryTitle: (r) => r?.name, }; const validations = { name: [], primaryAuthor: [], primaryTitle: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPrimaryAuthorRecords = async (value) => { setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listAuthors, variables, }) )?.data?.listAuthors?.items; var loaded = result.filter( (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); setPrimaryAuthorLoading(false); }; const fetchPrimaryTitleRecords = async (value) => { setPrimaryTitleLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listTitles, variables, }) )?.data?.listTitles?.items; var loaded = result.filter( (item) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPrimaryTitleRecords(newOptions.slice(0, autocompleteLength)); setPrimaryTitleLoading(false); }; React.useEffect(() => { fetchPrimaryAuthorRecords(\\"\\"); fetchPrimaryTitleRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { name, primaryAuthor, primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: createBook, variables: { input: { ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { name: value, primaryAuthor, primaryTitle, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor: value, primaryTitle, }; const result = onChange(modelFields); value = result?.primaryAuthor ?? value; } setPrimaryAuthor(value); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} items={primaryAuthor ? [primaryAuthor] : []} hasError={errors?.primaryAuthor?.hasError} errorMessage={errors?.primaryAuthor?.errorMessage} getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { setCurrentPrimaryAuthorDisplayValue( model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); setCurrentPrimaryAuthorValue(model); }} inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) ) .map((r) => ({ id: getIDValue.primaryAuthor?.(r), label: getDisplayValue.primaryAuthor?.(r), }))} isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( primaryAuthorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryAuthorDisplayValue(label); runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPrimaryAuthorRecords(value); if (errors.primaryAuthor?.hasError) { runValidationTasks(\\"primaryAuthor\\", value); } setCurrentPrimaryAuthorDisplayValue(value); setCurrentPrimaryAuthorValue(undefined); }} onBlur={() => runValidationTasks( \\"primaryAuthor\\", currentPrimaryAuthorDisplayValue ) } errorMessage={errors.primaryAuthor?.errorMessage} hasError={errors.primaryAuthor?.hasError} ref={primaryAuthorRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryAuthor\\")} > { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor, primaryTitle: value, }; const result = onChange(modelFields); value = result?.primaryTitle ?? value; } setPrimaryTitle(value); setCurrentPrimaryTitleValue(undefined); setCurrentPrimaryTitleDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryTitleValue} label={\\"Primary title\\"} items={primaryTitle ? [primaryTitle] : []} hasError={errors?.primaryTitle?.hasError} errorMessage={errors?.primaryTitle?.errorMessage} getBadgeText={getDisplayValue.primaryTitle} setFieldValue={(model) => { setCurrentPrimaryTitleDisplayValue( model ? getDisplayValue.primaryTitle(model) : \\"\\" ); setCurrentPrimaryTitleValue(model); }} inputFieldRef={primaryTitleRef} defaultFieldValue={\\"\\"} > !primaryTitleIdSet.has(getIDValue.primaryTitle?.(r))) .map((r) => ({ id: getIDValue.primaryTitle?.(r), label: getDisplayValue.primaryTitle?.(r), }))} isLoading={primaryTitleLoading} onSelect={({ id, label }) => { setCurrentPrimaryTitleValue( primaryTitleRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryTitleDisplayValue(label); runValidationTasks(\\"primaryTitle\\", label); }} onClear={() => { setCurrentPrimaryTitleDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPrimaryTitleRecords(value); if (errors.primaryTitle?.hasError) { runValidationTasks(\\"primaryTitle\\", value); } setCurrentPrimaryTitleDisplayValue(value); setCurrentPrimaryTitleValue(undefined); }} onBlur={() => runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) } errorMessage={errors.primaryTitle?.errorMessage} hasError={errors.primaryTitle?.hasError} ref={primaryTitleRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryTitle\\")} > ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Author, Title } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; export declare type BookCreateFormInputValues = { name?: string; primaryAuthor?: Author; primaryTitle?: Title; }; export declare type BookCreateFormValidationValues = { name?: ValidationFunction; primaryAuthor?: ValidationFunction; primaryTitle?: ValidationFunction; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type BookCreateFormOverridesProps = { BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type BookCreateFormProps = React.PropsWithChildren<{ overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onSuccess?: (fields: BookCreateFormInputValues) => void; onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getPost, listComments } from \\"../graphql/queries\\"; import { updateComment, updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function PostUpdateForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { title: \\"\\", body: \\"\\", publishDate: \\"\\", Comments: [], }; const [title, setTitle] = React.useState(initialValues.title); const [body, setBody] = React.useState(initialValues.body); const [publishDate, setPublishDate] = React.useState( initialValues.publishDate ); const [Comments, setComments] = React.useState(initialValues.Comments); const [CommentsLoading, setCommentsLoading] = React.useState(false); const [CommentsRecords, setCommentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord ? { ...initialValues, ...postRecord, Comments: linkedComments } : initialValues; setTitle(cleanValues.title); setBody(cleanValues.body); setPublishDate(cleanValues.publishDate); setComments(cleanValues.Comments ?? []); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); const [linkedComments, setLinkedComments] = React.useState([]); const canUnlinkComments = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getPost, variables: { id: idProp }, }) )?.data?.getPost : postModelProp; const linkedComments = record?.Comment?.items ?? []; setLinkedComments(linkedComments); setPostRecord(record); }; queryData(); }, [idProp, postModelProp]); React.useEffect(resetStateValues, [postRecord, linkedComments]); const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = React.useState(\\"\\"); const [currentCommentsValue, setCurrentCommentsValue] = React.useState(undefined); const CommentsRef = React.createRef(); const getIDValue = { Comments: (r) => JSON.stringify({ id: r?.id }), }; const CommentsIdSet = new Set( Array.isArray(Comments) ? Comments.map((r) => getIDValue.Comments?.(r)) : getIDValue.Comments?.(Comments) ); const getDisplayValue = { Comments: (r) => \`\${r?.body ? r?.body + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { title: [], body: [], publishDate: [], Comments: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const convertToLocal = (date) => { const df = new Intl.DateTimeFormat(\\"default\\", { year: \\"numeric\\", month: \\"2-digit\\", day: \\"2-digit\\", hour: \\"2-digit\\", minute: \\"2-digit\\", calendar: \\"iso8601\\", numberingSystem: \\"latn\\", hourCycle: \\"h23\\", }); const parts = df.formatToParts(date).reduce((acc, part) => { acc[part.type] = part.value; return acc; }, {}); return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; }; const fetchCommentsRecords = async (value) => { setCommentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ body: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listComments, variables, }) )?.data?.listComments?.items; var loaded = result.filter( (item) => !CommentsIdSet.has(getIDValue.Comments?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCommentsRecords(newOptions.slice(0, autocompleteLength)); setCommentsLoading(false); }; React.useEffect(() => { fetchCommentsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { title, body, publishDate, Comments, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const commentsToLink = []; const commentsToUnLink = []; const commentsSet = new Set(); const linkedCommentsSet = new Set(); Comments.forEach((r) => commentsSet.add(getIDValue.Comments?.(r))); linkedComments.forEach((r) => linkedCommentsSet.add(getIDValue.Comments?.(r)) ); linkedComments.forEach((r) => { if (!commentsSet.has(getIDValue.Comments?.(r))) { commentsToUnLink.push(r); } }); Comments.forEach((r) => { if (!linkedCommentsSet.has(getIDValue.Comments?.(r))) { commentsToLink.push(r); } }); commentsToUnLink.forEach((original) => { if (!canUnlinkComments) { throw Error( \`Comment \${original.id} cannot be unlinked from Post because postID is a required field.\` ); } promises.push( API.graphql({ query: updateComment, variables: { input: { id: original.id, postID: null, }, }, }) ); }); commentsToLink.forEach((original) => { promises.push( API.graphql({ query: updateComment, variables: { input: { id: original.id, postID: postRecord.id, }, }, }) ); }); const modelFieldsToSave = { title: modelFields.title, body: modelFields.body, publishDate: modelFields.publishDate, }; promises.push( API.graphql({ query: updatePost, variables: { input: { id: postRecord.id, ...modelFieldsToSave, }, }, }) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"PostUpdateForm\\")} {...rest} > <TextField label=\\"Title\\" isRequired={false} isReadOnly={false} value={title} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { title: value, body, publishDate, Comments, }; const result = onChange(modelFields); value = result?.title ?? value; } if (errors.title?.hasError) { runValidationTasks(\\"title\\", value); } setTitle(value); }} onBlur={() => runValidationTasks(\\"title\\", title)} errorMessage={errors.title?.errorMessage} hasError={errors.title?.hasError} {...getOverrideProps(overrides, \\"title\\")} ></TextField> <TextField label=\\"Body\\" isRequired={false} isReadOnly={false} value={body} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { title, body: value, publishDate, Comments, }; const result = onChange(modelFields); value = result?.body ?? value; } if (errors.body?.hasError) { runValidationTasks(\\"body\\", value); } setBody(value); }} onBlur={() => runValidationTasks(\\"body\\", body)} errorMessage={errors.body?.errorMessage} hasError={errors.body?.hasError} {...getOverrideProps(overrides, \\"body\\")} ></TextField> <TextField label=\\"Publish date\\" isRequired={false} isReadOnly={false} type=\\"datetime-local\\" value={publishDate && convertToLocal(new Date(publishDate))} onChange={(e) => { let value = e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); if (onChange) { const modelFields = { title, body, publishDate: value, Comments, }; const result = onChange(modelFields); value = result?.publishDate ?? value; } if (errors.publishDate?.hasError) { runValidationTasks(\\"publishDate\\", value); } setPublishDate(value); }} onBlur={() => runValidationTasks(\\"publishDate\\", publishDate)} errorMessage={errors.publishDate?.errorMessage} hasError={errors.publishDate?.hasError} {...getOverrideProps(overrides, \\"publishDate\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { title, body, publishDate, Comments: values, }; const result = onChange(modelFields); values = result?.Comments ?? values; } setComments(values); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); }} currentFieldValue={currentCommentsValue} label={\\"Comments\\"} items={Comments} hasError={errors?.Comments?.hasError} errorMessage={errors?.Comments?.errorMessage} getBadgeText={getDisplayValue.Comments} setFieldValue={(model) => { setCurrentCommentsDisplayValue( model ? getDisplayValue.Comments(model) : \\"\\" ); setCurrentCommentsValue(model); }} inputFieldRef={CommentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Comments\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Comment\\" value={currentCommentsDisplayValue} options={CommentsRecords.map((r) => ({ id: getIDValue.Comments?.(r), label: getDisplayValue.Comments?.(r), }))} isLoading={CommentsLoading} onSelect={({ id, label }) => { setCurrentCommentsValue( CommentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCommentsDisplayValue(label); runValidationTasks(\\"Comments\\", label); }} onClear={() => { setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCommentsRecords(value); if (errors.Comments?.hasError) { runValidationTasks(\\"Comments\\", value); } setCurrentCommentsDisplayValue(value); setCurrentCommentsValue(undefined); }} onBlur={() => runValidationTasks(\\"Comments\\", currentCommentsDisplayValue) } errorMessage={errors.Comments?.errorMessage} hasError={errors.Comments?.hasError} ref={CommentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Comments\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type PostUpdateFormInputValues = { title?: string; body?: string; publishDate?: string; Comments?: Comment[]; }; export declare type PostUpdateFormValidationValues = { title?: ValidationFunction<string>; body?: ValidationFunction<string>; publishDate?: ValidationFunction<string>; Comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type PostUpdateFormOverridesProps = { PostUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; title?: PrimitiveOverrideProps<TextFieldProps>; body?: PrimitiveOverrideProps<TextFieldProps>; publishDate?: PrimitiveOverrideProps<TextFieldProps>; Comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type PostUpdateFormProps = React.PropsWithChildren<{ overrides?: PostUpdateFormOverridesProps | undefined | null; } & { id?: string; post?: Post; onSubmit?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; onSuccess?: (fields: PostUpdateFormInputValues) => void; onError?: (fields: PostUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; onValidate?: PostUpdateFormValidationValues; } & React.CSSProperties>; export default function PostUpdateForm(props: PostUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getPost } from \\"../graphql/queries\\"; import { updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyPostForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { TextAreaFieldbbd63464: \\"\\", caption: \\"\\", username: \\"\\", profile_url: \\"\\", post_url: \\"\\", metadata: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( initialValues.TextAreaFieldbbd63464 ); const [caption, setCaption] = React.useState(initialValues.caption); const [username, setUsername] = React.useState(initialValues.username); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord ? { ...initialValues, ...postRecord } : initialValues; setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); setCaption(cleanValues.caption); setUsername(cleanValues.username); setProfile_url(cleanValues.profile_url); setPost_url(cleanValues.post_url); setMetadata( typeof cleanValues.metadata === \\"string\\" || cleanValues.metadata === null ? cleanValues.metadata : JSON.stringify(cleanValues.metadata) ); setNonModelField( typeof cleanValues.nonModelField === \\"string\\" || cleanValues.nonModelField === null ? cleanValues.nonModelField : JSON.stringify(cleanValues.nonModelField) ); setNonModelFieldArray( cleanValues.nonModelFieldArray?.map((item) => typeof item === \\"string\\" ? item : JSON.stringify(item) ) ?? [] ); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getPost, variables: { id: idProp }, }) )?.data?.getPost : postModelProp; setPostRecord(record); }; queryData(); }, [idProp, postModelProp]); React.useEffect(resetStateValues, [postRecord]); const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { TextAreaFieldbbd63464: [], caption: [], username: [], profile_url: [{ type: \\"URL\\" }], post_url: [{ type: \\"URL\\" }], metadata: [{ type: \\"JSON\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { caption: modelFields.caption, username: modelFields.username, profile_url: modelFields.profile_url, post_url: modelFields.post_url, metadata: modelFields.metadata, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await API.graphql({ query: updatePost, variables: { input: { id: postRecord.id, ...modelFieldsToSave, }, }, }); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextAreaField label=\\"Label\\" value={TextAreaFieldbbd63464} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464: value, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.TextAreaFieldbbd63464 ?? value; } if (errors.TextAreaFieldbbd63464?.hasError) { runValidationTasks(\\"TextAreaFieldbbd63464\\", value); } setTextAreaFieldbbd63464(value); }} onBlur={() => runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) } errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} hasError={errors.TextAreaFieldbbd63464?.hasError} {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} ></TextAreaField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption: value, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username: value, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url: value, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> <TextField label=\\"Post url\\" isRequired={false} isReadOnly={false} value={post_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url: value, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} ></TextField> <TextAreaField label=\\"Metadata\\" isRequired={false} isReadOnly={false} value={metadata} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} ></TextAreaField> <TextAreaField label=\\"Non model field\\" isRequired={false} isReadOnly={false} value={nonModelField} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Non model field array\\" isRequired={false} isReadOnly={false} value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyPostFormInputValues = { TextAreaFieldbbd63464?: string; caption?: string; username?: string; profile_url?: string; post_url?: string; metadata?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type MyPostFormValidationValues = { TextAreaFieldbbd63464?: ValidationFunction<string>; caption?: ValidationFunction<string>; username?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; post_url?: ValidationFunction<string>; metadata?: ValidationFunction<string>; nonModelField?: ValidationFunction<string>; nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyPostFormOverridesProps = { MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; username?: PrimitiveOverrideProps<TextFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; post_url?: PrimitiveOverrideProps<TextFieldProps>; metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; export declare type MyPostFormProps = React.PropsWithChildren<{ overrides?: MyPostFormOverridesProps | undefined | null; } & { id?: string; post?: Post; onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onSuccess?: (fields: MyPostFormInputValues) => void; onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { getMovie, listMovieTags, listTags, movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre, } from \\"../graphql/queries\\"; import { API } from \\"aws-amplify\\"; import { createMovieTags, deleteMovieTags, updateMovie, } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MovieUpdateForm(props) { const { id: idProp, movie: movieModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { movieKey: \\"\\", title: \\"\\", genre: \\"\\", rating: \\"\\", tags: [], }; const [movieKey, setMovieKey] = React.useState(initialValues.movieKey); const [title, setTitle] = React.useState(initialValues.title); const [genre, setGenre] = React.useState(initialValues.genre); const [rating, setRating] = React.useState(initialValues.rating); const [tags, setTags] = React.useState(initialValues.tags); const [tagsLoading, setTagsLoading] = React.useState(false); const [tagsRecords, setTagsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = movieRecord ? { ...initialValues, ...movieRecord, tags: linkedTags } : initialValues; setMovieKey(cleanValues.movieKey); setTitle(cleanValues.title); setGenre(cleanValues.genre); setRating(cleanValues.rating); setTags(cleanValues.tags ?? []); setCurrentTagsValue(undefined); setCurrentTagsDisplayValue(\\"\\"); setErrors({}); }; const [movieRecord, setMovieRecord] = React.useState(movieModelProp); const [linkedTags, setLinkedTags] = React.useState([]); const canUnlinkTags = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getMovie, variables: { ...idProp }, }) )?.data?.getMovie : movieModelProp; const linkedTags = record ? ( await API.graphql({ query: movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre, variables: { movieMovieKey: record.movieKey, movietitle: record.title, moviegenre: record.genre, }, }) ).data.movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre.items.map( (t) => t.tag ) : []; setLinkedTags(linkedTags); setMovieRecord(record); }; queryData(); }, [idProp, movieModelProp]); React.useEffect(resetStateValues, [movieRecord, linkedTags]); const [currentTagsDisplayValue, setCurrentTagsDisplayValue] = React.useState(\\"\\"); const [currentTagsValue, setCurrentTagsValue] = React.useState(undefined); const tagsRef = React.createRef(); const getIDValue = { tags: (r) => JSON.stringify({ id: r?.id }), }; const tagsIdSet = new Set( Array.isArray(tags) ? tags.map((r) => getIDValue.tags?.(r)) : getIDValue.tags?.(tags) ); const getDisplayValue = { tags: (r) => \`\${r?.label ? r?.label + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { movieKey: [{ type: \\"Required\\" }], title: [{ type: \\"Required\\" }], genre: [{ type: \\"Required\\" }], rating: [], tags: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchTagsRecords = async (value) => { setTagsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ label: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listTags, variables, }) )?.data?.listTags?.items; var loaded = result.filter( (item) => !tagsIdSet.has(getIDValue.tags?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setTagsRecords(newOptions.slice(0, autocompleteLength)); setTagsLoading(false); }; React.useEffect(() => { fetchTagsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { movieKey, title, genre, rating, tags, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const tagsToLinkMap = new Map(); const tagsToUnLinkMap = new Map(); const tagsMap = new Map(); const linkedTagsMap = new Map(); tags.forEach((r) => { const count = tagsMap.get(getIDValue.tags?.(r)); const newCount = count ? count + 1 : 1; tagsMap.set(getIDValue.tags?.(r), newCount); }); linkedTags.forEach((r) => { const count = linkedTagsMap.get(getIDValue.tags?.(r)); const newCount = count ? count + 1 : 1; linkedTagsMap.set(getIDValue.tags?.(r), newCount); }); linkedTagsMap.forEach((count, id) => { const newCount = tagsMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { tagsToUnLinkMap.set(id, diffCount); } } else { tagsToUnLinkMap.set(id, count); } }); tagsMap.forEach((count, id) => { const originalCount = linkedTagsMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { tagsToLinkMap.set(id, diffCount); } } else { tagsToLinkMap.set(id, count); } }); tagsToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const movieTagsRecords = ( await API.graphql({ query: listMovieTags, variables: { filter: { and: [ { tagId: { eq: recordKeys.id } }, { movieMovieKey: { eq: movieRecord.movieKey } }, { movietitle: { eq: movieRecord.title } }, { moviegenre: { eq: movieRecord.genre } }, ], }, }, }) )?.data?.listMovieTags?.items; for (let i = 0; i < count; i++) { promises.push( API.graphql({ query: deleteMovieTags, variables: { input: { id: movieTagsRecords[i].id, }, }, }) ); } }); tagsToLinkMap.forEach((count, id) => { const tagToLink = tagRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( API.graphql({ query: createMovieTags, variables: { input: { movieMovieKey: movieRecord.movieKey, movietitle: movieRecord.title, moviegenre: movieRecord.genre, tagId: tagToLink.id, }, }, }) ); } }); const modelFieldsToSave = { movieKey: modelFields.movieKey, title: modelFields.title, genre: modelFields.genre, rating: modelFields.rating, }; promises.push( API.graphql({ query: updateMovie, variables: { input: { movieKey: movieRecord.movieKey, title: movieRecord.title, genre: movieRecord.genre, ...modelFieldsToSave, }, }, }) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MovieUpdateForm\\")} {...rest} > <TextField label=\\"Movie key\\" isRequired={true} isReadOnly={true} value={movieKey} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { movieKey: value, title, genre, rating, tags, }; const result = onChange(modelFields); value = result?.movieKey ?? value; } if (errors.movieKey?.hasError) { runValidationTasks(\\"movieKey\\", value); } setMovieKey(value); }} onBlur={() => runValidationTasks(\\"movieKey\\", movieKey)} errorMessage={errors.movieKey?.errorMessage} hasError={errors.movieKey?.hasError} {...getOverrideProps(overrides, \\"movieKey\\")} ></TextField> <TextField label=\\"Title\\" isRequired={true} isReadOnly={true} value={title} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { movieKey, title: value, genre, rating, tags, }; const result = onChange(modelFields); value = result?.title ?? value; } if (errors.title?.hasError) { runValidationTasks(\\"title\\", value); } setTitle(value); }} onBlur={() => runValidationTasks(\\"title\\", title)} errorMessage={errors.title?.errorMessage} hasError={errors.title?.hasError} {...getOverrideProps(overrides, \\"title\\")} ></TextField> <TextField label=\\"Genre\\" isRequired={true} isReadOnly={true} value={genre} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { movieKey, title, genre: value, rating, tags, }; const result = onChange(modelFields); value = result?.genre ?? value; } if (errors.genre?.hasError) { runValidationTasks(\\"genre\\", value); } setGenre(value); }} onBlur={() => runValidationTasks(\\"genre\\", genre)} errorMessage={errors.genre?.errorMessage} hasError={errors.genre?.hasError} {...getOverrideProps(overrides, \\"genre\\")} ></TextField> <TextField label=\\"Rating\\" isRequired={false} isReadOnly={false} value={rating} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { movieKey, title, genre, rating: value, tags, }; const result = onChange(modelFields); value = result?.rating ?? value; } if (errors.rating?.hasError) { runValidationTasks(\\"rating\\", value); } setRating(value); }} onBlur={() => runValidationTasks(\\"rating\\", rating)} errorMessage={errors.rating?.errorMessage} hasError={errors.rating?.hasError} {...getOverrideProps(overrides, \\"rating\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { movieKey, title, genre, rating, tags: values, }; const result = onChange(modelFields); values = result?.tags ?? values; } setTags(values); setCurrentTagsValue(undefined); setCurrentTagsDisplayValue(\\"\\"); }} currentFieldValue={currentTagsValue} label={\\"Tags\\"} items={tags} hasError={errors?.tags?.hasError} errorMessage={errors?.tags?.errorMessage} getBadgeText={getDisplayValue.tags} setFieldValue={(model) => { setCurrentTagsDisplayValue(model ? getDisplayValue.tags(model) : \\"\\"); setCurrentTagsValue(model); }} inputFieldRef={tagsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Tags\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Tag\\" value={currentTagsDisplayValue} options={tagsRecords.map((r) => ({ id: getIDValue.tags?.(r), label: getDisplayValue.tags?.(r), }))} isLoading={tagsLoading} onSelect={({ id, label }) => { setCurrentTagsValue( tagsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentTagsDisplayValue(label); runValidationTasks(\\"tags\\", label); }} onClear={() => { setCurrentTagsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchTagsRecords(value); if (errors.tags?.hasError) { runValidationTasks(\\"tags\\", value); } setCurrentTagsDisplayValue(value); setCurrentTagsValue(undefined); }} onBlur={() => runValidationTasks(\\"tags\\", currentTagsDisplayValue)} errorMessage={errors.tags?.errorMessage} hasError={errors.tags?.hasError} ref={tagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"tags\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || movieModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || movieModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Movie, Tag } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MovieUpdateFormInputValues = { movieKey?: string; title?: string; genre?: string; rating?: string; tags?: Tag[]; }; export declare type MovieUpdateFormValidationValues = { movieKey?: ValidationFunction<string>; title?: ValidationFunction<string>; genre?: ValidationFunction<string>; rating?: ValidationFunction<string>; tags?: ValidationFunction<Tag>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MovieUpdateFormOverridesProps = { MovieUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; movieKey?: PrimitiveOverrideProps<TextFieldProps>; title?: PrimitiveOverrideProps<TextFieldProps>; genre?: PrimitiveOverrideProps<TextFieldProps>; rating?: PrimitiveOverrideProps<TextFieldProps>; tags?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type MovieUpdateFormProps = React.PropsWithChildren<{ overrides?: MovieUpdateFormOverridesProps | undefined | null; } & { id?: { movieKey: string; title: string; genre: string; }; movie?: Movie; onSubmit?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; onSuccess?: (fields: MovieUpdateFormInputValues) => void; onError?: (fields: MovieUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; onValidate?: MovieUpdateFormValidationValues; } & React.CSSProperties>; export default function MovieUpdateForm(props: MovieUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getComment, getPost, listPosts } from \\"../graphql/queries\\"; import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CommentUpdateForm(props) { const { id: idProp, comment: commentModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { content: \\"\\", postID: undefined, Post: undefined, post: \\"\\", }; const [content, setContent] = React.useState(initialValues.content); const [postID, setPostID] = React.useState(initialValues.postID); const [postIDLoading, setPostIDLoading] = React.useState(false); const [postIDRecords, setPostIDRecords] = React.useState([]); const [Post, setPost] = React.useState(initialValues.Post); const [PostLoading, setPostLoading] = React.useState(false); const [PostRecords, setPostRecords] = React.useState([]); const [post1, setPost1] = React.useState(initialValues.post); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = commentRecord ? { ...initialValues, ...commentRecord, postID, Post } : initialValues; setContent(cleanValues.content); setPostID(cleanValues.postID); setCurrentPostIDValue(undefined); setCurrentPostIDDisplayValue(\\"\\"); setPost(cleanValues.Post); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); setPost1(cleanValues.post); setErrors({}); }; const [commentRecord, setCommentRecord] = React.useState(commentModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getComment, variables: { id: idProp }, }) )?.data?.getComment : commentModelProp; const postIDRecord = record ? record.postID : undefined; const postRecord = postIDRecord ? ( await API.graphql({ query: getPost, variables: { id: postIDRecord }, }) )?.data?.getPost : undefined; setPostID(postIDRecord); setPostIDRecords([postRecord]); const PostRecord = record ? await record.Post : undefined; setPost(PostRecord); setCommentRecord(record); }; queryData(); }, [idProp, commentModelProp]); React.useEffect(resetStateValues, [commentRecord, postID, Post]); const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); const postIDRef = React.createRef(); const [currentPostDisplayValue, setCurrentPostDisplayValue] = React.useState(\\"\\"); const [currentPostValue, setCurrentPostValue] = React.useState(undefined); const PostRef = React.createRef(); const getIDValue = { Post: (r) => JSON.stringify({ id: r?.id }), }; const PostIdSet = new Set( Array.isArray(Post) ? Post.map((r) => getIDValue.Post?.(r)) : getIDValue.Post?.(Post) ); const getDisplayValue = { postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, Post: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { content: [], postID: [{ type: \\"Required\\" }], Post: [], post: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPostIDRecords = async (value) => { setPostIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter((item) => postID !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } setPostIDRecords(newOptions.slice(0, autocompleteLength)); setPostIDLoading(false); }; const fetchPostRecords = async (value) => { setPostLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter( (item) => !PostIdSet.has(getIDValue.Post?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPostRecords(newOptions.slice(0, autocompleteLength)); setPostLoading(false); }; React.useEffect(() => { fetchPostIDRecords(\\"\\"); fetchPostRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { content, postID, Post, post: post1, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { content: modelFields.content, postID: modelFields.postID, Post: modelFields.Post, }; await API.graphql({ query: updateComment, variables: { input: { id: commentRecord.id, ...modelFieldsToSave, }, }, }); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CommentUpdateForm\\")} {...rest} > <TextField label=\\"Content\\" isRequired={false} isReadOnly={false} value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { content: value, postID, Post, post: post1, }; const result = onChange(modelFields); value = result?.content ?? value; } if (errors.content?.hasError) { runValidationTasks(\\"content\\", value); } setContent(value); }} onBlur={() => runValidationTasks(\\"content\\", content)} errorMessage={errors.content?.errorMessage} hasError={errors.content?.hasError} {...getOverrideProps(overrides, \\"content\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { content, postID: value, Post, post: post1, }; const result = onChange(modelFields); value = result?.postID ?? value; } setPostID(value); setCurrentPostIDValue(undefined); }} currentFieldValue={currentPostIDValue} label={\\"Post id\\"} items={postID ? [postID] : []} hasError={errors?.postID?.hasError} errorMessage={errors?.postID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.postID(postIDRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentPostIDDisplayValue( value ? getDisplayValue.postID( postIDRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentPostIDValue(value); }} inputFieldRef={postIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostIDDisplayValue} options={postIDRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.postID?.(r), }))} isLoading={postIDLoading} onSelect={({ id, label }) => { setCurrentPostIDValue(id); setCurrentPostIDDisplayValue(label); runValidationTasks(\\"postID\\", label); }} onClear={() => { setCurrentPostIDDisplayValue(\\"\\"); }} defaultValue={postID} onChange={(e) => { let { value } = e.target; fetchPostIDRecords(value); if (errors.postID?.hasError) { runValidationTasks(\\"postID\\", value); } setCurrentPostIDDisplayValue(value); setCurrentPostIDValue(undefined); }} onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} errorMessage={errors.postID?.errorMessage} hasError={errors.postID?.hasError} ref={postIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"postID\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { content, postID, Post: value, post: post1, }; const result = onChange(modelFields); value = result?.Post ?? value; } setPost(value); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); }} currentFieldValue={currentPostValue} label={\\"Post\\"} items={Post ? [Post] : []} hasError={errors?.Post?.hasError} errorMessage={errors?.Post?.errorMessage} getBadgeText={getDisplayValue.Post} setFieldValue={(model) => { setCurrentPostDisplayValue(model ? getDisplayValue.Post(model) : \\"\\"); setCurrentPostValue(model); }} inputFieldRef={PostRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostDisplayValue} options={PostRecords.filter( (r) => !PostIdSet.has(getIDValue.Post?.(r)) ).map((r) => ({ id: getIDValue.Post?.(r), label: getDisplayValue.Post?.(r), }))} isLoading={PostLoading} onSelect={({ id, label }) => { setCurrentPostValue( PostRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostDisplayValue(label); runValidationTasks(\\"Post\\", label); }} onClear={() => { setCurrentPostDisplayValue(\\"\\"); }} defaultValue={Post} onChange={(e) => { let { value } = e.target; fetchPostRecords(value); if (errors.Post?.hasError) { runValidationTasks(\\"Post\\", value); } setCurrentPostDisplayValue(value); setCurrentPostValue(undefined); }} onBlur={() => runValidationTasks(\\"Post\\", currentPostDisplayValue)} errorMessage={errors.Post?.errorMessage} hasError={errors.Post?.hasError} ref={PostRef} labelHidden={true} {...getOverrideProps(overrides, \\"Post\\")} ></Autocomplete> </ArrayField> <TextField label=\\"Label\\" value={post1} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { content, postID, Post, post: value, }; const result = onChange(modelFields); value = result?.post ?? value; } if (errors.post?.hasError) { runValidationTasks(\\"post\\", value); } setPost1(value); }} onBlur={() => runValidationTasks(\\"post\\", post1)} errorMessage={errors.post?.errorMessage} hasError={errors.post?.hasError} {...getOverrideProps(overrides, \\"post\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || commentModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || commentModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CommentUpdateFormInputValues = { content?: string; postID?: string; Post?: Post; post?: string; }; export declare type CommentUpdateFormValidationValues = { content?: ValidationFunction<string>; postID?: ValidationFunction<string>; Post?: ValidationFunction<Post>; post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CommentUpdateFormOverridesProps = { CommentUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; content?: PrimitiveOverrideProps<TextFieldProps>; postID?: PrimitiveOverrideProps<AutocompleteProps>; Post?: PrimitiveOverrideProps<AutocompleteProps>; post?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type CommentUpdateFormProps = React.PropsWithChildren<{ overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; comment?: Comment; onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onSuccess?: (fields: CommentUpdateFormInputValues) => void; onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onValidate?: CommentUpdateFormValidationValues; } & React.CSSProperties>; export default function CommentUpdateForm(props: CommentUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getComment, getPost, listPosts } from \\"../graphql/queries\\"; import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CommentUpdateForm(props) { const { id: idProp, comment: commentModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { content: \\"\\", postID: undefined, Post: undefined, post: \\"\\", }; const [content, setContent] = React.useState(initialValues.content); const [postID, setPostID] = React.useState(initialValues.postID); const [postIDLoading, setPostIDLoading] = React.useState(false); const [postIDRecords, setPostIDRecords] = React.useState([]); const [Post, setPost] = React.useState(initialValues.Post); const [PostLoading, setPostLoading] = React.useState(false); const [PostRecords, setPostRecords] = React.useState([]); const [post1, setPost1] = React.useState(initialValues.post); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = commentRecord ? { ...initialValues, ...commentRecord, postID, Post } : initialValues; setContent(cleanValues.content); setPostID(cleanValues.postID); setCurrentPostIDValue(undefined); setCurrentPostIDDisplayValue(\\"\\"); setPost(cleanValues.Post); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); setPost1(cleanValues.post); setErrors({}); }; const [commentRecord, setCommentRecord] = React.useState(commentModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getComment, variables: { id: idProp }, }) )?.data?.getComment : commentModelProp; const postIDRecord = record ? record.postID : undefined; const postRecord = postIDRecord ? ( await API.graphql({ query: getPost, variables: { id: postIDRecord }, }) )?.data?.getPost : undefined; setPostID(postIDRecord); setPostIDRecords([postRecord]); const PostRecord = record ? await record.Post : undefined; setPost(PostRecord); setCommentRecord(record); }; queryData(); }, [idProp, commentModelProp]); React.useEffect(resetStateValues, [commentRecord, postID, Post]); const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); const postIDRef = React.createRef(); const [currentPostDisplayValue, setCurrentPostDisplayValue] = React.useState(\\"\\"); const [currentPostValue, setCurrentPostValue] = React.useState(undefined); const PostRef = React.createRef(); const getIDValue = { Post: (r) => JSON.stringify({ id: r?.id }), }; const PostIdSet = new Set( Array.isArray(Post) ? Post.map((r) => getIDValue.Post?.(r)) : getIDValue.Post?.(Post) ); const getDisplayValue = { postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, Post: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { content: [], postID: [{ type: \\"Required\\" }], Post: [], post: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPostIDRecords = async (value) => { setPostIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter((item) => postID !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } setPostIDRecords(newOptions.slice(0, autocompleteLength)); setPostIDLoading(false); }; const fetchPostRecords = async (value) => { setPostLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter( (item) => !PostIdSet.has(getIDValue.Post?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPostRecords(newOptions.slice(0, autocompleteLength)); setPostLoading(false); }; React.useEffect(() => { fetchPostIDRecords(\\"\\"); fetchPostRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { content, postID, Post, post: post1, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { content: modelFields.content, postID: modelFields.postID, Post: modelFields.Post, }; await API.graphql({ query: updateComment, variables: { input: { id: commentRecord.id, ...modelFieldsToSave, }, }, }); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CommentUpdateForm\\")} {...rest} > <TextField label=\\"Content\\" isRequired={false} isReadOnly={false} value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { content: value, postID, Post, post: post1, }; const result = onChange(modelFields); value = result?.content ?? value; } if (errors.content?.hasError) { runValidationTasks(\\"content\\", value); } setContent(value); }} onBlur={() => runValidationTasks(\\"content\\", content)} errorMessage={errors.content?.errorMessage} hasError={errors.content?.hasError} {...getOverrideProps(overrides, \\"content\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { content, postID: value, Post, post: post1, }; const result = onChange(modelFields); value = result?.postID ?? value; } setPostID(value); setCurrentPostIDValue(undefined); }} currentFieldValue={currentPostIDValue} label={\\"Post id\\"} items={postID ? [postID] : []} hasError={errors?.postID?.hasError} errorMessage={errors?.postID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.postID(postIDRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentPostIDDisplayValue( value ? getDisplayValue.postID( postIDRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentPostIDValue(value); }} inputFieldRef={postIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostIDDisplayValue} options={postIDRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.postID?.(r), }))} isLoading={postIDLoading} onSelect={({ id, label }) => { setCurrentPostIDValue(id); setCurrentPostIDDisplayValue(label); runValidationTasks(\\"postID\\", label); }} onClear={() => { setCurrentPostIDDisplayValue(\\"\\"); }} defaultValue={postID} onChange={(e) => { let { value } = e.target; fetchPostIDRecords(value); if (errors.postID?.hasError) { runValidationTasks(\\"postID\\", value); } setCurrentPostIDDisplayValue(value); setCurrentPostIDValue(undefined); }} onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} errorMessage={errors.postID?.errorMessage} hasError={errors.postID?.hasError} ref={postIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"postID\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { content, postID, Post: value, post: post1, }; const result = onChange(modelFields); value = result?.Post ?? value; } setPost(value); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); }} currentFieldValue={currentPostValue} label={\\"Post\\"} items={Post ? [Post] : []} hasError={errors?.Post?.hasError} errorMessage={errors?.Post?.errorMessage} getBadgeText={getDisplayValue.Post} setFieldValue={(model) => { setCurrentPostDisplayValue(model ? getDisplayValue.Post(model) : \\"\\"); setCurrentPostValue(model); }} inputFieldRef={PostRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostDisplayValue} options={PostRecords.filter( (r) => !PostIdSet.has(getIDValue.Post?.(r)) ).map((r) => ({ id: getIDValue.Post?.(r), label: getDisplayValue.Post?.(r), }))} isLoading={PostLoading} onSelect={({ id, label }) => { setCurrentPostValue( PostRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostDisplayValue(label); runValidationTasks(\\"Post\\", label); }} onClear={() => { setCurrentPostDisplayValue(\\"\\"); }} defaultValue={Post} onChange={(e) => { let { value } = e.target; fetchPostRecords(value); if (errors.Post?.hasError) { runValidationTasks(\\"Post\\", value); } setCurrentPostDisplayValue(value); setCurrentPostValue(undefined); }} onBlur={() => runValidationTasks(\\"Post\\", currentPostDisplayValue)} errorMessage={errors.Post?.errorMessage} hasError={errors.Post?.hasError} ref={PostRef} labelHidden={true} {...getOverrideProps(overrides, \\"Post\\")} ></Autocomplete> </ArrayField> <TextField label=\\"Label\\" value={post1} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { content, postID, Post, post: value, }; const result = onChange(modelFields); value = result?.post ?? value; } if (errors.post?.hasError) { runValidationTasks(\\"post\\", value); } setPost1(value); }} onBlur={() => runValidationTasks(\\"post\\", post1)} errorMessage={errors.post?.errorMessage} hasError={errors.post?.hasError} {...getOverrideProps(overrides, \\"post\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || commentModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || commentModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CommentUpdateFormInputValues = { content?: string; postID?: string; Post?: any; post?: string; }; export declare type CommentUpdateFormValidationValues = { content?: ValidationFunction<string>; postID?: ValidationFunction<string>; Post?: ValidationFunction<any>; post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CommentUpdateFormOverridesProps = { CommentUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; content?: PrimitiveOverrideProps<TextFieldProps>; postID?: PrimitiveOverrideProps<AutocompleteProps>; Post?: PrimitiveOverrideProps<AutocompleteProps>; post?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type CommentUpdateFormProps = React.PropsWithChildren<{ overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; comment?: any; onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onSuccess?: (fields: CommentUpdateFormInputValues) => void; onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onValidate?: CommentUpdateFormValidationValues; } & React.CSSProperties>; export default function CommentUpdateForm(props: CommentUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with many to many relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { getClass, listStudentClasses, listStudents, studentClassByClassId, } from \\"../graphql/queries\\"; import { API } from \\"aws-amplify\\"; import { createStudentClass, deleteStudentClass, updateClass, } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function ClassUpdateForm(props) { const { id: idProp, class: classModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { students: [], }; const [students, setStudents] = React.useState(initialValues.students); const [studentsLoading, setStudentsLoading] = React.useState(false); const [studentsRecords, setStudentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = classRecord ? { ...initialValues, ...classRecord, students: linkedStudents } : initialValues; setStudents(cleanValues.students ?? []); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); setErrors({}); }; const [classRecord, setClassRecord] = React.useState(classModelProp); const [linkedStudents, setLinkedStudents] = React.useState([]); const canUnlinkStudents = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getClass, variables: { id: idProp }, }) )?.data?.getClass : classModelProp; const linkedStudents = record ? ( await API.graphql({ query: studentClassByClassId, variables: { classId: record.id, }, }) ).data.studentClassByClassId.items.map((t) => t.student) : []; setLinkedStudents(linkedStudents); setClassRecord(record); }; queryData(); }, [idProp, classModelProp]); React.useEffect(resetStateValues, [classRecord, linkedStudents]); const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = React.useState(\\"\\"); const [currentStudentsValue, setCurrentStudentsValue] = React.useState(undefined); const studentsRef = React.createRef(); const getIDValue = { students: (r) => JSON.stringify({ id: r?.id }), }; const studentsIdSet = new Set( Array.isArray(students) ? students.map((r) => getIDValue.students?.(r)) : getIDValue.students?.(students) ); const getDisplayValue = { students: (r) => r?.id, }; const validations = { students: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchStudentsRecords = async (value) => { setStudentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ id: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listStudents, variables, }) )?.data?.listStudents?.items; var loaded = result.filter( (item) => !studentsIdSet.has(getIDValue.students?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setStudentsRecords(newOptions.slice(0, autocompleteLength)); setStudentsLoading(false); }; React.useEffect(() => { fetchStudentsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { students, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const studentsToLinkMap = new Map(); const studentsToUnLinkMap = new Map(); const studentsMap = new Map(); const linkedStudentsMap = new Map(); students.forEach((r) => { const count = studentsMap.get(getIDValue.students?.(r)); const newCount = count ? count + 1 : 1; studentsMap.set(getIDValue.students?.(r), newCount); }); linkedStudents.forEach((r) => { const count = linkedStudentsMap.get(getIDValue.students?.(r)); const newCount = count ? count + 1 : 1; linkedStudentsMap.set(getIDValue.students?.(r), newCount); }); linkedStudentsMap.forEach((count, id) => { const newCount = studentsMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { studentsToUnLinkMap.set(id, diffCount); } } else { studentsToUnLinkMap.set(id, count); } }); studentsMap.forEach((count, id) => { const originalCount = linkedStudentsMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { studentsToLinkMap.set(id, diffCount); } } else { studentsToLinkMap.set(id, count); } }); studentsToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const studentClassRecords = ( await API.graphql({ query: listStudentClasses, variables: { filter: { and: [ { studentId: { eq: recordKeys.id } }, { classId: { eq: classRecord.id } }, ], }, }, }) )?.data?.listStudentClasses?.items; for (let i = 0; i < count; i++) { promises.push( API.graphql({ query: deleteStudentClass, variables: { input: { id: studentClassRecords[i].id, }, }, }) ); } }); studentsToLinkMap.forEach((count, id) => { const studentToLink = studentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( API.graphql({ query: createStudentClass, variables: { input: { classId: classRecord.id, studentId: studentToLink.id, }, }, }) ); } }); const modelFieldsToSave = {}; promises.push( API.graphql({ query: updateClass, variables: { input: { id: classRecord.id, ...modelFieldsToSave, }, }, }) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"ClassUpdateForm\\")} {...rest} > <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { students: values, }; const result = onChange(modelFields); values = result?.students ?? values; } setStudents(values); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); }} currentFieldValue={currentStudentsValue} label={\\"Students\\"} items={students} hasError={errors?.students?.hasError} errorMessage={errors?.students?.errorMessage} getBadgeText={getDisplayValue.students} setFieldValue={(model) => { setCurrentStudentsDisplayValue( model ? getDisplayValue.students(model) : \\"\\" ); setCurrentStudentsValue(model); }} inputFieldRef={studentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Students\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Student\\" value={currentStudentsDisplayValue} options={studentsRecords.map((r) => ({ id: getIDValue.students?.(r), label: getDisplayValue.students?.(r), }))} isLoading={studentsLoading} onSelect={({ id, label }) => { setCurrentStudentsValue( studentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentStudentsDisplayValue(label); runValidationTasks(\\"students\\", label); }} onClear={() => { setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchStudentsRecords(value); if (errors.students?.hasError) { runValidationTasks(\\"students\\", value); } setCurrentStudentsDisplayValue(value); setCurrentStudentsValue(undefined); }} onBlur={() => runValidationTasks(\\"students\\", currentStudentsDisplayValue) } errorMessage={errors.students?.errorMessage} hasError={errors.students?.hasError} ref={studentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"students\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || classModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || classModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate an update form with many to many relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Class, Student } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type ClassUpdateFormInputValues = { students?: Student[]; }; export declare type ClassUpdateFormValidationValues = { students?: ValidationFunction<Student>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type ClassUpdateFormOverridesProps = { ClassUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; students?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type ClassUpdateFormProps = React.PropsWithChildren<{ overrides?: ClassUpdateFormOverridesProps | undefined | null; } & { id?: string; class?: Class; onSubmit?: (fields: ClassUpdateFormInputValues) => ClassUpdateFormInputValues; onSuccess?: (fields: ClassUpdateFormInputValues) => void; onError?: (fields: ClassUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: ClassUpdateFormInputValues) => ClassUpdateFormInputValues; onValidate?: ClassUpdateFormValidationValues; } & React.CSSProperties>; export default function ClassUpdateForm(props: ClassUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should generate an upgrade form with multiple relationship & cpk 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { cPKTeacherCPKClassByCPKTeacherSpecialTeacherId, getCPKTeacher, listCPKClasses, listCPKProjects, listCPKStudents, listCPKTeacherCPKClasses, } from \\"../graphql/queries\\"; import { API } from \\"aws-amplify\\"; import { createCPKTeacherCPKClass, deleteCPKTeacherCPKClass, updateCPKProject, updateCPKTeacher, } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateCPKTeacherForm(props) { const { specialTeacherId: specialTeacherIdProp, cPKTeacher: cPKTeacherModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { specialTeacherId: \\"\\", CPKStudent: undefined, CPKClasses: [], CPKProjects: [], }; const [specialTeacherId, setSpecialTeacherId] = React.useState( initialValues.specialTeacherId ); const [CPKStudent, setCPKStudent] = React.useState(initialValues.CPKStudent); const [CPKStudentLoading, setCPKStudentLoading] = React.useState(false); const [CPKStudentRecords, setCPKStudentRecords] = React.useState([]); const [CPKClasses, setCPKClasses] = React.useState(initialValues.CPKClasses); const [CPKClassesLoading, setCPKClassesLoading] = React.useState(false); const [CPKClassesRecords, setCPKClassesRecords] = React.useState([]); const [CPKProjects, setCPKProjects] = React.useState( initialValues.CPKProjects ); const [CPKProjectsLoading, setCPKProjectsLoading] = React.useState(false); const [CPKProjectsRecords, setCPKProjectsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = cPKTeacherRecord ? { ...initialValues, ...cPKTeacherRecord, CPKStudent, CPKClasses: linkedCPKClasses, CPKProjects: linkedCPKProjects, } : initialValues; setSpecialTeacherId(cleanValues.specialTeacherId); setCPKStudent(cleanValues.CPKStudent); setCurrentCPKStudentValue(undefined); setCurrentCPKStudentDisplayValue(\\"\\"); setCPKClasses(cleanValues.CPKClasses ?? []); setCurrentCPKClassesValue(undefined); setCurrentCPKClassesDisplayValue(\\"\\"); setCPKProjects(cleanValues.CPKProjects ?? []); setCurrentCPKProjectsValue(undefined); setCurrentCPKProjectsDisplayValue(\\"\\"); setErrors({}); }; const [cPKTeacherRecord, setCPKTeacherRecord] = React.useState(cPKTeacherModelProp); const [linkedCPKClasses, setLinkedCPKClasses] = React.useState([]); const canUnlinkCPKClasses = false; const [linkedCPKProjects, setLinkedCPKProjects] = React.useState([]); const canUnlinkCPKProjects = true; React.useEffect(() => { const queryData = async () => { const record = specialTeacherIdProp ? ( await API.graphql({ query: getCPKTeacher, variables: { id: specialTeacherIdProp }, }) )?.data?.getCPKTeacher : cPKTeacherModelProp; const CPKStudentRecord = record ? await record.CPKStudent : undefined; setCPKStudent(CPKStudentRecord); const linkedCPKClasses = record ? ( await API.graphql({ query: cPKTeacherCPKClassByCPKTeacherSpecialTeacherId, variables: { cPKTeacherSpecialTeacherId: record.specialTeacherId, }, }) ).data.cPKTeacherCPKClassByCPKTeacherSpecialTeacherId.items.map( (t) => t.cpkClass ) : []; setLinkedCPKClasses(linkedCPKClasses); const linkedCPKProjects = record?.CPKProject?.items ?? []; setLinkedCPKProjects(linkedCPKProjects); setCPKTeacherRecord(record); }; queryData(); }, [specialTeacherIdProp, cPKTeacherModelProp]); React.useEffect(resetStateValues, [ cPKTeacherRecord, CPKStudent, linkedCPKClasses, linkedCPKProjects, ]); const [currentCPKStudentDisplayValue, setCurrentCPKStudentDisplayValue] = React.useState(\\"\\"); const [currentCPKStudentValue, setCurrentCPKStudentValue] = React.useState(undefined); const CPKStudentRef = React.createRef(); const [currentCPKClassesDisplayValue, setCurrentCPKClassesDisplayValue] = React.useState(\\"\\"); const [currentCPKClassesValue, setCurrentCPKClassesValue] = React.useState(undefined); const CPKClassesRef = React.createRef(); const [currentCPKProjectsDisplayValue, setCurrentCPKProjectsDisplayValue] = React.useState(\\"\\"); const [currentCPKProjectsValue, setCurrentCPKProjectsValue] = React.useState(undefined); const CPKProjectsRef = React.createRef(); const getIDValue = { CPKStudent: (r) => JSON.stringify({ specialStudentId: r?.specialStudentId }), CPKClasses: (r) => JSON.stringify({ specialClassId: r?.specialClassId }), CPKProjects: (r) => JSON.stringify({ specialProjectId: r?.specialProjectId }), }; const CPKStudentIdSet = new Set( Array.isArray(CPKStudent) ? CPKStudent.map((r) => getIDValue.CPKStudent?.(r)) : getIDValue.CPKStudent?.(CPKStudent) ); const CPKClassesIdSet = new Set( Array.isArray(CPKClasses) ? CPKClasses.map((r) => getIDValue.CPKClasses?.(r)) : getIDValue.CPKClasses?.(CPKClasses) ); const CPKProjectsIdSet = new Set( Array.isArray(CPKProjects) ? CPKProjects.map((r) => getIDValue.CPKProjects?.(r)) : getIDValue.CPKProjects?.(CPKProjects) ); const getDisplayValue = { CPKStudent: (r) => r?.specialStudentId, CPKClasses: (r) => r?.specialClassId, CPKProjects: (r) => r?.specialProjectId, }; const validations = { specialTeacherId: [{ type: \\"Required\\" }], CPKStudent: [], CPKClasses: [], CPKProjects: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchCPKStudentRecords = async (value) => { setCPKStudentLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ specialStudentId: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCPKStudents, variables, }) )?.data?.listCPKStudents?.items; var loaded = result.filter( (item) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCPKStudentRecords(newOptions.slice(0, autocompleteLength)); setCPKStudentLoading(false); }; const fetchCPKClassesRecords = async (value) => { setCPKClassesLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ specialClassId: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCPKClasses, variables, }) )?.data?.listCPKClasses?.items; var loaded = result.filter( (item) => !CPKClassesIdSet.has(getIDValue.CPKClasses?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCPKClassesRecords(newOptions.slice(0, autocompleteLength)); setCPKClassesLoading(false); }; const fetchCPKProjectsRecords = async (value) => { setCPKProjectsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ specialProjectId: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCPKProjects, variables, }) )?.data?.listCPKProjects?.items; var loaded = result.filter( (item) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCPKProjectsRecords(newOptions.slice(0, autocompleteLength)); setCPKProjectsLoading(false); }; React.useEffect(() => { fetchCPKStudentRecords(\\"\\"); fetchCPKClassesRecords(\\"\\"); fetchCPKProjectsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { specialTeacherId, CPKStudent, CPKClasses, CPKProjects, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const cPKClassesToLinkMap = new Map(); const cPKClassesToUnLinkMap = new Map(); const cPKClassesMap = new Map(); const linkedCPKClassesMap = new Map(); CPKClasses.forEach((r) => { const count = cPKClassesMap.get(getIDValue.CPKClasses?.(r)); const newCount = count ? count + 1 : 1; cPKClassesMap.set(getIDValue.CPKClasses?.(r), newCount); }); linkedCPKClasses.forEach((r) => { const count = linkedCPKClassesMap.get(getIDValue.CPKClasses?.(r)); const newCount = count ? count + 1 : 1; linkedCPKClassesMap.set(getIDValue.CPKClasses?.(r), newCount); }); linkedCPKClassesMap.forEach((count, id) => { const newCount = cPKClassesMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { cPKClassesToUnLinkMap.set(id, diffCount); } } else { cPKClassesToUnLinkMap.set(id, count); } }); cPKClassesMap.forEach((count, id) => { const originalCount = linkedCPKClassesMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { cPKClassesToLinkMap.set(id, diffCount); } } else { cPKClassesToLinkMap.set(id, count); } }); cPKClassesToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const cPKTeacherCPKClassRecords = ( await API.graphql({ query: listCPKTeacherCPKClasses, variables: { filter: { and: [ { cPKClassSpecialClassId: { eq: recordKeys.specialClassId, }, }, { cPKTeacherSpecialTeacherId: { eq: cPKTeacherRecord.specialTeacherId, }, }, ], }, }, }) )?.data?.listCPKTeacherCPKClasses?.items; for (let i = 0; i < count; i++) { promises.push( API.graphql({ query: deleteCPKTeacherCPKClass, variables: { input: { id: cPKTeacherCPKClassRecords[i].id, }, }, }) ); } }); cPKClassesToLinkMap.forEach((count, id) => { const cPKClassToLink = cPKClassRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( API.graphql({ query: createCPKTeacherCPKClass, variables: { input: { cPKTeacherSpecialTeacherId: cPKTeacherRecord.specialTeacherId, cPKClassSpecialClassId: cPKClassToLink.specialClassId, }, }, }) ); } }); const cPKProjectsToLink = []; const cPKProjectsToUnLink = []; const cPKProjectsSet = new Set(); const linkedCPKProjectsSet = new Set(); CPKProjects.forEach((r) => cPKProjectsSet.add(getIDValue.CPKProjects?.(r)) ); linkedCPKProjects.forEach((r) => linkedCPKProjectsSet.add(getIDValue.CPKProjects?.(r)) ); linkedCPKProjects.forEach((r) => { if (!cPKProjectsSet.has(getIDValue.CPKProjects?.(r))) { cPKProjectsToUnLink.push(r); } }); CPKProjects.forEach((r) => { if (!linkedCPKProjectsSet.has(getIDValue.CPKProjects?.(r))) { cPKProjectsToLink.push(r); } }); cPKProjectsToUnLink.forEach((original) => { if (!canUnlinkCPKProjects) { throw Error( \`CPKProject \${original.specialProjectId} cannot be unlinked from CPKTeacher because cPKTeacherID is a required field.\` ); } promises.push( API.graphql({ query: updateCPKProject, variables: { input: { specialProjectId: original.specialProjectId, cPKTeacherID: null, }, }, }) ); }); cPKProjectsToLink.forEach((original) => { promises.push( API.graphql({ query: updateCPKProject, variables: { input: { specialProjectId: original.specialProjectId, cPKTeacherID: cPKTeacherRecord.specialTeacherId, }, }, }) ); }); const modelFieldsToSave = { specialTeacherId: modelFields.specialTeacherId, CPKStudent: modelFields.CPKStudent, }; promises.push( API.graphql({ query: updateCPKTeacher, variables: { input: { specialTeacherId: cPKTeacherRecord.specialTeacherId, ...modelFieldsToSave, }, }, }) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateCPKTeacherForm\\")} {...rest} > <TextField label=\\"Special teacher id\\" isRequired={true} isReadOnly={true} value={specialTeacherId} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { specialTeacherId: value, CPKStudent, CPKClasses, CPKProjects, }; const result = onChange(modelFields); value = result?.specialTeacherId ?? value; } if (errors.specialTeacherId?.hasError) { runValidationTasks(\\"specialTeacherId\\", value); } setSpecialTeacherId(value); }} onBlur={() => runValidationTasks(\\"specialTeacherId\\", specialTeacherId)} errorMessage={errors.specialTeacherId?.errorMessage} hasError={errors.specialTeacherId?.hasError} {...getOverrideProps(overrides, \\"specialTeacherId\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { specialTeacherId, CPKStudent: value, CPKClasses, CPKProjects, }; const result = onChange(modelFields); value = result?.CPKStudent ?? value; } setCPKStudent(value); setCurrentCPKStudentValue(undefined); setCurrentCPKStudentDisplayValue(\\"\\"); }} currentFieldValue={currentCPKStudentValue} label={\\"Cpk student\\"} items={CPKStudent ? [CPKStudent] : []} hasError={errors?.CPKStudent?.hasError} errorMessage={errors?.CPKStudent?.errorMessage} getBadgeText={getDisplayValue.CPKStudent} setFieldValue={(model) => { setCurrentCPKStudentDisplayValue( model ? getDisplayValue.CPKStudent(model) : \\"\\" ); setCurrentCPKStudentValue(model); }} inputFieldRef={CPKStudentRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk student\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKStudent\\" value={currentCPKStudentDisplayValue} options={CPKStudentRecords.filter( (r) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(r)) ).map((r) => ({ id: getIDValue.CPKStudent?.(r), label: getDisplayValue.CPKStudent?.(r), }))} isLoading={CPKStudentLoading} onSelect={({ id, label }) => { setCurrentCPKStudentValue( CPKStudentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKStudentDisplayValue(label); runValidationTasks(\\"CPKStudent\\", label); }} onClear={() => { setCurrentCPKStudentDisplayValue(\\"\\"); }} defaultValue={CPKStudent} onChange={(e) => { let { value } = e.target; fetchCPKStudentRecords(value); if (errors.CPKStudent?.hasError) { runValidationTasks(\\"CPKStudent\\", value); } setCurrentCPKStudentDisplayValue(value); setCurrentCPKStudentValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKStudent\\", currentCPKStudentDisplayValue) } errorMessage={errors.CPKStudent?.errorMessage} hasError={errors.CPKStudent?.hasError} ref={CPKStudentRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKStudent\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { specialTeacherId, CPKStudent, CPKClasses: values, CPKProjects, }; const result = onChange(modelFields); values = result?.CPKClasses ?? values; } setCPKClasses(values); setCurrentCPKClassesValue(undefined); setCurrentCPKClassesDisplayValue(\\"\\"); }} currentFieldValue={currentCPKClassesValue} label={\\"Cpk classes\\"} items={CPKClasses} hasError={errors?.CPKClasses?.hasError} errorMessage={errors?.CPKClasses?.errorMessage} getBadgeText={getDisplayValue.CPKClasses} setFieldValue={(model) => { setCurrentCPKClassesDisplayValue( model ? getDisplayValue.CPKClasses(model) : \\"\\" ); setCurrentCPKClassesValue(model); }} inputFieldRef={CPKClassesRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk classes\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKClass\\" value={currentCPKClassesDisplayValue} options={CPKClassesRecords.map((r) => ({ id: getIDValue.CPKClasses?.(r), label: getDisplayValue.CPKClasses?.(r), }))} isLoading={CPKClassesLoading} onSelect={({ id, label }) => { setCurrentCPKClassesValue( CPKClassesRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKClassesDisplayValue(label); runValidationTasks(\\"CPKClasses\\", label); }} onClear={() => { setCurrentCPKClassesDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCPKClassesRecords(value); if (errors.CPKClasses?.hasError) { runValidationTasks(\\"CPKClasses\\", value); } setCurrentCPKClassesDisplayValue(value); setCurrentCPKClassesValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKClasses\\", currentCPKClassesDisplayValue) } errorMessage={errors.CPKClasses?.errorMessage} hasError={errors.CPKClasses?.hasError} ref={CPKClassesRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKClasses\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { specialTeacherId, CPKStudent, CPKClasses, CPKProjects: values, }; const result = onChange(modelFields); values = result?.CPKProjects ?? values; } setCPKProjects(values); setCurrentCPKProjectsValue(undefined); setCurrentCPKProjectsDisplayValue(\\"\\"); }} currentFieldValue={currentCPKProjectsValue} label={\\"Cpk projects\\"} items={CPKProjects} hasError={errors?.CPKProjects?.hasError} errorMessage={errors?.CPKProjects?.errorMessage} getBadgeText={getDisplayValue.CPKProjects} setFieldValue={(model) => { setCurrentCPKProjectsDisplayValue( model ? getDisplayValue.CPKProjects(model) : \\"\\" ); setCurrentCPKProjectsValue(model); }} inputFieldRef={CPKProjectsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk projects\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKProject\\" value={currentCPKProjectsDisplayValue} options={CPKProjectsRecords.filter( (r) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(r)) ).map((r) => ({ id: getIDValue.CPKProjects?.(r), label: getDisplayValue.CPKProjects?.(r), }))} isLoading={CPKProjectsLoading} onSelect={({ id, label }) => { setCurrentCPKProjectsValue( CPKProjectsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKProjectsDisplayValue(label); runValidationTasks(\\"CPKProjects\\", label); }} onClear={() => { setCurrentCPKProjectsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCPKProjectsRecords(value); if (errors.CPKProjects?.hasError) { runValidationTasks(\\"CPKProjects\\", value); } setCurrentCPKProjectsDisplayValue(value); setCurrentCPKProjectsValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKProjects\\", currentCPKProjectsDisplayValue) } errorMessage={errors.CPKProjects?.errorMessage} hasError={errors.CPKProjects?.hasError} ref={CPKProjectsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKProjects\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(specialTeacherIdProp || cPKTeacherModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(specialTeacherIdProp || cPKTeacherModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should generate an upgrade form with multiple relationship & cpk 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CPKClass, CPKProject, CPKStudent, CPKTeacher } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateCPKTeacherFormInputValues = { specialTeacherId?: string; CPKStudent?: CPKStudent; CPKClasses?: CPKClass[]; CPKProjects?: CPKProject[]; }; export declare type UpdateCPKTeacherFormValidationValues = { specialTeacherId?: ValidationFunction<string>; CPKStudent?: ValidationFunction<CPKStudent>; CPKClasses?: ValidationFunction<CPKClass>; CPKProjects?: ValidationFunction<CPKProject>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateCPKTeacherFormOverridesProps = { UpdateCPKTeacherFormGrid?: PrimitiveOverrideProps<GridProps>; specialTeacherId?: PrimitiveOverrideProps<TextFieldProps>; CPKStudent?: PrimitiveOverrideProps<AutocompleteProps>; CPKClasses?: PrimitiveOverrideProps<AutocompleteProps>; CPKProjects?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateCPKTeacherFormProps = React.PropsWithChildren<{ overrides?: UpdateCPKTeacherFormOverridesProps | undefined | null; } & { specialTeacherId?: string; cPKTeacher?: CPKTeacher; onSubmit?: (fields: UpdateCPKTeacherFormInputValues) => UpdateCPKTeacherFormInputValues; onSuccess?: (fields: UpdateCPKTeacherFormInputValues) => void; onError?: (fields: UpdateCPKTeacherFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateCPKTeacherFormInputValues) => UpdateCPKTeacherFormInputValues; onValidate?: UpdateCPKTeacherFormValidationValues; } & React.CSSProperties>; export default function UpdateCPKTeacherForm(props: UpdateCPKTeacherFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for child of 1:m relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listCompositeDogs } from \\"../graphql/queries\\"; import { createCompositeToy } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCompositeToyForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { kind: \\"\\", color: \\"\\", compositeDogCompositeToysName: undefined, compositeDogCompositeToysDescription: undefined, }; const [kind, setKind] = React.useState(initialValues.kind); const [color, setColor] = React.useState(initialValues.color); const [compositeDogCompositeToysName, setCompositeDogCompositeToysName] = React.useState(initialValues.compositeDogCompositeToysName); const [ compositeDogCompositeToysNameLoading, setCompositeDogCompositeToysNameLoading, ] = React.useState(false); const [ compositeDogCompositeToysNameRecords, setCompositeDogCompositeToysNameRecords, ] = React.useState([]); const [ compositeDogCompositeToysDescription, setCompositeDogCompositeToysDescription, ] = React.useState(initialValues.compositeDogCompositeToysDescription); const [ compositeDogCompositeToysDescriptionLoading, setCompositeDogCompositeToysDescriptionLoading, ] = React.useState(false); const [ compositeDogCompositeToysDescriptionRecords, setCompositeDogCompositeToysDescriptionRecords, ] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setKind(initialValues.kind); setColor(initialValues.color); setCompositeDogCompositeToysName( initialValues.compositeDogCompositeToysName ); setCurrentCompositeDogCompositeToysNameValue(undefined); setCurrentCompositeDogCompositeToysNameDisplayValue(\\"\\"); setCompositeDogCompositeToysDescription( initialValues.compositeDogCompositeToysDescription ); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); setCurrentCompositeDogCompositeToysDescriptionDisplayValue(\\"\\"); setErrors({}); }; const [ currentCompositeDogCompositeToysNameDisplayValue, setCurrentCompositeDogCompositeToysNameDisplayValue, ] = React.useState(\\"\\"); const [ currentCompositeDogCompositeToysNameValue, setCurrentCompositeDogCompositeToysNameValue, ] = React.useState(undefined); const compositeDogCompositeToysNameRef = React.createRef(); const [ currentCompositeDogCompositeToysDescriptionDisplayValue, setCurrentCompositeDogCompositeToysDescriptionDisplayValue, ] = React.useState(\\"\\"); const [ currentCompositeDogCompositeToysDescriptionValue, setCurrentCompositeDogCompositeToysDescriptionValue, ] = React.useState(undefined); const compositeDogCompositeToysDescriptionRef = React.createRef(); const getDisplayValue = { compositeDogCompositeToysName: (r) => r?.name, compositeDogCompositeToysDescription: (r) => r?.description, }; const validations = { kind: [{ type: \\"Required\\" }], color: [{ type: \\"Required\\" }], compositeDogCompositeToysName: [], compositeDogCompositeToysDescription: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchCompositeDogCompositeToysNameRecords = async (value) => { setCompositeDogCompositeToysNameLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeDogs, variables, }) )?.data?.listCompositeDogs?.items; var loaded = result.filter( (item) => compositeDogCompositeToysName !== item.id ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeDogCompositeToysNameRecords( newOptions.slice(0, autocompleteLength) ); setCompositeDogCompositeToysNameLoading(false); }; const fetchCompositeDogCompositeToysDescriptionRecords = async (value) => { setCompositeDogCompositeToysDescriptionLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ description: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeDogs, variables, }) )?.data?.listCompositeDogs?.items; var loaded = result.filter( (item) => compositeDogCompositeToysDescription !== item.id ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeDogCompositeToysDescriptionRecords( newOptions.slice(0, autocompleteLength) ); setCompositeDogCompositeToysDescriptionLoading(false); }; React.useEffect(() => { fetchCompositeDogCompositeToysNameRecords(\\"\\"); fetchCompositeDogCompositeToysDescriptionRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { kind, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: createCompositeToy, variables: { input: { ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCompositeToyForm\\")} {...rest} > <TextField label=\\"Kind\\" isRequired={true} isReadOnly={false} value={kind} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { kind: value, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.kind ?? value; } if (errors.kind?.hasError) { runValidationTasks(\\"kind\\", value); } setKind(value); }} onBlur={() => runValidationTasks(\\"kind\\", kind)} errorMessage={errors.kind?.errorMessage} hasError={errors.kind?.hasError} {...getOverrideProps(overrides, \\"kind\\")} ></TextField> <TextField label=\\"Color\\" isRequired={true} isReadOnly={false} value={color} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { kind, color: value, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.color ?? value; } if (errors.color?.hasError) { runValidationTasks(\\"color\\", value); } setColor(value); }} onBlur={() => runValidationTasks(\\"color\\", color)} errorMessage={errors.color?.errorMessage} hasError={errors.color?.hasError} {...getOverrideProps(overrides, \\"color\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { kind, color, compositeDogCompositeToysName: value, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.compositeDogCompositeToysName ?? value; } setCompositeDogCompositeToysName(value); setCurrentCompositeDogCompositeToysNameValue(undefined); }} currentFieldValue={currentCompositeDogCompositeToysNameValue} label={\\"Composite dog composite toys name\\"} items={ compositeDogCompositeToysName ? [compositeDogCompositeToysName] : [] } hasError={errors?.compositeDogCompositeToysName?.hasError} errorMessage={errors?.compositeDogCompositeToysName?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.compositeDogCompositeToysName( compositeDogCompositeToysNameRecords.find( (r) => r.name === value ) ) : \\"\\" } setFieldValue={(value) => { setCurrentCompositeDogCompositeToysNameDisplayValue( value ? getDisplayValue.compositeDogCompositeToysName( compositeDogCompositeToysNameRecords.find( (r) => r.name === value ) ) : \\"\\" ); setCurrentCompositeDogCompositeToysNameValue(value); }} inputFieldRef={compositeDogCompositeToysNameRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite dog composite toys name\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeDog\\" value={currentCompositeDogCompositeToysNameDisplayValue} options={compositeDogCompositeToysNameRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.name === r?.name) === i ) .map((r) => ({ id: r?.name, label: getDisplayValue.compositeDogCompositeToysName?.(r), }))} isLoading={compositeDogCompositeToysNameLoading} onSelect={({ id, label }) => { setCurrentCompositeDogCompositeToysNameValue(id); setCurrentCompositeDogCompositeToysNameDisplayValue(label); runValidationTasks(\\"compositeDogCompositeToysName\\", label); }} onClear={() => { setCurrentCompositeDogCompositeToysNameDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeDogCompositeToysNameRecords(value); if (errors.compositeDogCompositeToysName?.hasError) { runValidationTasks(\\"compositeDogCompositeToysName\\", value); } setCurrentCompositeDogCompositeToysNameDisplayValue(value); setCurrentCompositeDogCompositeToysNameValue(undefined); }} onBlur={() => runValidationTasks( \\"compositeDogCompositeToysName\\", currentCompositeDogCompositeToysNameValue ) } errorMessage={errors.compositeDogCompositeToysName?.errorMessage} hasError={errors.compositeDogCompositeToysName?.hasError} ref={compositeDogCompositeToysNameRef} labelHidden={true} {...getOverrideProps(overrides, \\"compositeDogCompositeToysName\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { kind, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription: value, }; const result = onChange(modelFields); value = result?.compositeDogCompositeToysDescription ?? value; } setCompositeDogCompositeToysDescription(value); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); }} currentFieldValue={currentCompositeDogCompositeToysDescriptionValue} label={\\"Composite dog composite toys description\\"} items={ compositeDogCompositeToysDescription ? [compositeDogCompositeToysDescription] : [] } hasError={errors?.compositeDogCompositeToysDescription?.hasError} errorMessage={ errors?.compositeDogCompositeToysDescription?.errorMessage } getBadgeText={(value) => value ? getDisplayValue.compositeDogCompositeToysDescription( compositeDogCompositeToysDescriptionRecords.find( (r) => r.description === value ) ) : \\"\\" } setFieldValue={(value) => { setCurrentCompositeDogCompositeToysDescriptionDisplayValue( value ? getDisplayValue.compositeDogCompositeToysDescription( compositeDogCompositeToysDescriptionRecords.find( (r) => r.description === value ) ) : \\"\\" ); setCurrentCompositeDogCompositeToysDescriptionValue(value); }} inputFieldRef={compositeDogCompositeToysDescriptionRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite dog composite toys description\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeDog\\" value={currentCompositeDogCompositeToysDescriptionDisplayValue} options={compositeDogCompositeToysDescriptionRecords .filter( (r, i, arr) => arr.findIndex( (member) => member?.description === r?.description ) === i ) .map((r) => ({ id: r?.description, label: getDisplayValue.compositeDogCompositeToysDescription?.(r), }))} isLoading={compositeDogCompositeToysDescriptionLoading} onSelect={({ id, label }) => { setCurrentCompositeDogCompositeToysDescriptionValue(id); setCurrentCompositeDogCompositeToysDescriptionDisplayValue(label); runValidationTasks(\\"compositeDogCompositeToysDescription\\", label); }} onClear={() => { setCurrentCompositeDogCompositeToysDescriptionDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeDogCompositeToysDescriptionRecords(value); if (errors.compositeDogCompositeToysDescription?.hasError) { runValidationTasks(\\"compositeDogCompositeToysDescription\\", value); } setCurrentCompositeDogCompositeToysDescriptionDisplayValue(value); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); }} onBlur={() => runValidationTasks( \\"compositeDogCompositeToysDescription\\", currentCompositeDogCompositeToysDescriptionValue ) } errorMessage={ errors.compositeDogCompositeToysDescription?.errorMessage } hasError={errors.compositeDogCompositeToysDescription?.hasError} ref={compositeDogCompositeToysDescriptionRef} labelHidden={true} {...getOverrideProps( overrides, \\"compositeDogCompositeToysDescription\\" )} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for child of 1:m relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCompositeToyFormInputValues = { kind?: string; color?: string; compositeDogCompositeToysName?: string; compositeDogCompositeToysDescription?: string; }; export declare type CreateCompositeToyFormValidationValues = { kind?: ValidationFunction<string>; color?: ValidationFunction<string>; compositeDogCompositeToysName?: ValidationFunction<string>; compositeDogCompositeToysDescription?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCompositeToyFormOverridesProps = { CreateCompositeToyFormGrid?: PrimitiveOverrideProps<GridProps>; kind?: PrimitiveOverrideProps<TextFieldProps>; color?: PrimitiveOverrideProps<TextFieldProps>; compositeDogCompositeToysName?: PrimitiveOverrideProps<AutocompleteProps>; compositeDogCompositeToysDescription?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCompositeToyFormProps = React.PropsWithChildren<{ overrides?: CreateCompositeToyFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCompositeToyFormInputValues) => CreateCompositeToyFormInputValues; onSuccess?: (fields: CreateCompositeToyFormInputValues) => void; onError?: (fields: CreateCompositeToyFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCompositeToyFormInputValues) => CreateCompositeToyFormInputValues; onValidate?: CreateCompositeToyFormValidationValues; } & React.CSSProperties>; export default function CreateCompositeToyForm(props: CreateCompositeToyFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for child of 1:m-belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listOrgs, listPosts, listUsers } from \\"../graphql/queries\\"; import { createComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCommentForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", post: undefined, User: undefined, Org: undefined, postCommentsId: undefined, }; const [name, setName] = React.useState(initialValues.name); const [post, setPost] = React.useState(initialValues.post); const [postLoading, setPostLoading] = React.useState(false); const [postRecords, setPostRecords] = React.useState([]); const [User, setUser] = React.useState(initialValues.User); const [UserLoading, setUserLoading] = React.useState(false); const [UserRecords, setUserRecords] = React.useState([]); const [Org, setOrg] = React.useState(initialValues.Org); const [OrgLoading, setOrgLoading] = React.useState(false); const [OrgRecords, setOrgRecords] = React.useState([]); const [postCommentsId, setPostCommentsId] = React.useState( initialValues.postCommentsId ); const [postCommentsIdLoading, setPostCommentsIdLoading] = React.useState(false); const [postCommentsIdRecords, setPostCommentsIdRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPost(initialValues.post); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); setUser(initialValues.User); setCurrentUserValue(undefined); setCurrentUserDisplayValue(\\"\\"); setOrg(initialValues.Org); setCurrentOrgValue(undefined); setCurrentOrgDisplayValue(\\"\\"); setPostCommentsId(initialValues.postCommentsId); setCurrentPostCommentsIdValue(undefined); setCurrentPostCommentsIdDisplayValue(\\"\\"); setErrors({}); }; const [currentPostDisplayValue, setCurrentPostDisplayValue] = React.useState(\\"\\"); const [currentPostValue, setCurrentPostValue] = React.useState(undefined); const postRef = React.createRef(); const [currentUserDisplayValue, setCurrentUserDisplayValue] = React.useState(\\"\\"); const [currentUserValue, setCurrentUserValue] = React.useState(undefined); const UserRef = React.createRef(); const [currentOrgDisplayValue, setCurrentOrgDisplayValue] = React.useState(\\"\\"); const [currentOrgValue, setCurrentOrgValue] = React.useState(undefined); const OrgRef = React.createRef(); const [ currentPostCommentsIdDisplayValue, setCurrentPostCommentsIdDisplayValue, ] = React.useState(\\"\\"); const [currentPostCommentsIdValue, setCurrentPostCommentsIdValue] = React.useState(undefined); const postCommentsIdRef = React.createRef(); const getIDValue = { post: (r) => JSON.stringify({ id: r?.id }), User: (r) => JSON.stringify({ id: r?.id }), Org: (r) => JSON.stringify({ id: r?.id }), }; const postIdSet = new Set( Array.isArray(post) ? post.map((r) => getIDValue.post?.(r)) : getIDValue.post?.(post) ); const UserIdSet = new Set( Array.isArray(User) ? User.map((r) => getIDValue.User?.(r)) : getIDValue.User?.(User) ); const OrgIdSet = new Set( Array.isArray(Org) ? Org.map((r) => getIDValue.Org?.(r)) : getIDValue.Org?.(Org) ); const getDisplayValue = { post: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, User: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Org: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, postCommentsId: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], post: [], User: [], Org: [{ type: \\"Required\\", validationMessage: \\"Org is required.\\" }], postCommentsId: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPostRecords = async (value) => { setPostLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter( (item) => !postIdSet.has(getIDValue.post?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setPostRecords(newOptions.slice(0, autocompleteLength)); setPostLoading(false); }; const fetchUserRecords = async (value) => { setUserLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listUsers, variables, }) )?.data?.listUsers?.items; var loaded = result.filter( (item) => !UserIdSet.has(getIDValue.User?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setUserRecords(newOptions.slice(0, autocompleteLength)); setUserLoading(false); }; const fetchOrgRecords = async (value) => { setOrgLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listOrgs, variables, }) )?.data?.listOrgs?.items; var loaded = result.filter( (item) => !OrgIdSet.has(getIDValue.Org?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setOrgRecords(newOptions.slice(0, autocompleteLength)); setOrgLoading(false); }; const fetchPostCommentsIdRecords = async (value) => { setPostCommentsIdLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter((item) => postCommentsId !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } setPostCommentsIdRecords(newOptions.slice(0, autocompleteLength)); setPostCommentsIdLoading(false); }; React.useEffect(() => { fetchPostRecords(\\"\\"); fetchUserRecords(\\"\\"); fetchOrgRecords(\\"\\"); fetchPostCommentsIdRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, post, User, Org, postCommentsId, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: createComment, variables: { input: { ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCommentForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, post, User, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post: value, User, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.post ?? value; } setPost(value); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); }} currentFieldValue={currentPostValue} label={\\"Post\\"} items={post ? [post] : []} hasError={errors?.post?.hasError} errorMessage={errors?.post?.errorMessage} getBadgeText={getDisplayValue.post} setFieldValue={(model) => { setCurrentPostDisplayValue(model ? getDisplayValue.post(model) : \\"\\"); setCurrentPostValue(model); }} inputFieldRef={postRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostDisplayValue} options={postRecords .filter((r) => !postIdSet.has(getIDValue.post?.(r))) .map((r) => ({ id: getIDValue.post?.(r), label: getDisplayValue.post?.(r), }))} isLoading={postLoading} onSelect={({ id, label }) => { setCurrentPostValue( postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostDisplayValue(label); runValidationTasks(\\"post\\", label); }} onClear={() => { setCurrentPostDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPostRecords(value); if (errors.post?.hasError) { runValidationTasks(\\"post\\", value); } setCurrentPostDisplayValue(value); setCurrentPostValue(undefined); }} onBlur={() => runValidationTasks(\\"post\\", currentPostDisplayValue)} errorMessage={errors.post?.errorMessage} hasError={errors.post?.hasError} ref={postRef} labelHidden={true} {...getOverrideProps(overrides, \\"post\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User: value, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.User ?? value; } setUser(value); setCurrentUserValue(undefined); setCurrentUserDisplayValue(\\"\\"); }} currentFieldValue={currentUserValue} label={\\"User\\"} items={User ? [User] : []} hasError={errors?.User?.hasError} errorMessage={errors?.User?.errorMessage} getBadgeText={getDisplayValue.User} setFieldValue={(model) => { setCurrentUserDisplayValue(model ? getDisplayValue.User(model) : \\"\\"); setCurrentUserValue(model); }} inputFieldRef={UserRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"User\\" isRequired={false} isReadOnly={false} placeholder=\\"Search User\\" value={currentUserDisplayValue} options={UserRecords.filter( (r) => !UserIdSet.has(getIDValue.User?.(r)) ).map((r) => ({ id: getIDValue.User?.(r), label: getDisplayValue.User?.(r), }))} isLoading={UserLoading} onSelect={({ id, label }) => { setCurrentUserValue( UserRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentUserDisplayValue(label); runValidationTasks(\\"User\\", label); }} onClear={() => { setCurrentUserDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchUserRecords(value); if (errors.User?.hasError) { runValidationTasks(\\"User\\", value); } setCurrentUserDisplayValue(value); setCurrentUserValue(undefined); }} onBlur={() => runValidationTasks(\\"User\\", currentUserDisplayValue)} errorMessage={errors.User?.errorMessage} hasError={errors.User?.hasError} ref={UserRef} labelHidden={true} {...getOverrideProps(overrides, \\"User\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User, Org: value, postCommentsId, }; const result = onChange(modelFields); value = result?.Org ?? value; } setOrg(value); setCurrentOrgValue(undefined); setCurrentOrgDisplayValue(\\"\\"); }} currentFieldValue={currentOrgValue} label={\\"Org\\"} items={Org ? [Org] : []} hasError={errors?.Org?.hasError} errorMessage={errors?.Org?.errorMessage} getBadgeText={getDisplayValue.Org} setFieldValue={(model) => { setCurrentOrgDisplayValue(model ? getDisplayValue.Org(model) : \\"\\"); setCurrentOrgValue(model); }} inputFieldRef={OrgRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Org\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Org\\" value={currentOrgDisplayValue} options={OrgRecords.filter( (r) => !OrgIdSet.has(getIDValue.Org?.(r)) ).map((r) => ({ id: getIDValue.Org?.(r), label: getDisplayValue.Org?.(r), }))} isLoading={OrgLoading} onSelect={({ id, label }) => { setCurrentOrgValue( OrgRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentOrgDisplayValue(label); runValidationTasks(\\"Org\\", label); }} onClear={() => { setCurrentOrgDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchOrgRecords(value); if (errors.Org?.hasError) { runValidationTasks(\\"Org\\", value); } setCurrentOrgDisplayValue(value); setCurrentOrgValue(undefined); }} onBlur={() => runValidationTasks(\\"Org\\", currentOrgDisplayValue)} errorMessage={errors.Org?.errorMessage} hasError={errors.Org?.hasError} ref={OrgRef} labelHidden={true} {...getOverrideProps(overrides, \\"Org\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User, Org, postCommentsId: value, }; const result = onChange(modelFields); value = result?.postCommentsId ?? value; } setPostCommentsId(value); setCurrentPostCommentsIdValue(undefined); }} currentFieldValue={currentPostCommentsIdValue} label={\\"Post comments id\\"} items={postCommentsId ? [postCommentsId] : []} hasError={errors?.postCommentsId?.hasError} errorMessage={errors?.postCommentsId?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.postCommentsId( postCommentsIdRecords.find((r) => r.id === value) ) : \\"\\" } setFieldValue={(value) => { setCurrentPostCommentsIdDisplayValue( value ? getDisplayValue.postCommentsId( postCommentsIdRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentPostCommentsIdValue(value); }} inputFieldRef={postCommentsIdRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post comments id\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostCommentsIdDisplayValue} options={postCommentsIdRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.postCommentsId?.(r), }))} isLoading={postCommentsIdLoading} onSelect={({ id, label }) => { setCurrentPostCommentsIdValue(id); setCurrentPostCommentsIdDisplayValue(label); runValidationTasks(\\"postCommentsId\\", label); }} onClear={() => { setCurrentPostCommentsIdDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchPostCommentsIdRecords(value); if (errors.postCommentsId?.hasError) { runValidationTasks(\\"postCommentsId\\", value); } setCurrentPostCommentsIdDisplayValue(value); setCurrentPostCommentsIdValue(undefined); }} onBlur={() => runValidationTasks(\\"postCommentsId\\", currentPostCommentsIdValue) } errorMessage={errors.postCommentsId?.errorMessage} hasError={errors.postCommentsId?.hasError} ref={postCommentsIdRef} labelHidden={true} {...getOverrideProps(overrides, \\"postCommentsId\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for child of 1:m-belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Org, Post, User } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCommentFormInputValues = { name?: string; post?: Post; User?: User; Org?: Org; postCommentsId?: string; }; export declare type CreateCommentFormValidationValues = { name?: ValidationFunction<string>; post?: ValidationFunction<Post>; User?: ValidationFunction<User>; Org?: ValidationFunction<Org>; postCommentsId?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCommentFormOverridesProps = { CreateCommentFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; post?: PrimitiveOverrideProps<AutocompleteProps>; User?: PrimitiveOverrideProps<AutocompleteProps>; Org?: PrimitiveOverrideProps<AutocompleteProps>; postCommentsId?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCommentFormProps = React.PropsWithChildren<{ overrides?: CreateCommentFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCommentFormInputValues) => CreateCommentFormInputValues; onSuccess?: (fields: CreateCommentFormInputValues) => void; onError?: (fields: CreateCommentFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCommentFormInputValues) => CreateCommentFormInputValues; onValidate?: CreateCommentFormValidationValues; } & React.CSSProperties>; export default function CreateCommentForm(props: CreateCommentFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for model with composite keys 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listCompositeBowls, listCompositeOwners, listCompositeToys, listCompositeVets, } from \\"../graphql/queries\\"; import { createCompositeDog, createCompositeDogCompositeVet, updateCompositeDog, updateCompositeOwner, } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCompositeDogForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", description: \\"\\", CompositeBowl: undefined, CompositeOwner: undefined, CompositeToys: [], CompositeVets: [], }; const [name, setName] = React.useState(initialValues.name); const [description, setDescription] = React.useState( initialValues.description ); const [CompositeBowl, setCompositeBowl] = React.useState( initialValues.CompositeBowl ); const [CompositeBowlLoading, setCompositeBowlLoading] = React.useState(false); const [CompositeBowlRecords, setCompositeBowlRecords] = React.useState([]); const [CompositeOwner, setCompositeOwner] = React.useState( initialValues.CompositeOwner ); const [CompositeOwnerLoading, setCompositeOwnerLoading] = React.useState(false); const [CompositeOwnerRecords, setCompositeOwnerRecords] = React.useState([]); const [CompositeToys, setCompositeToys] = React.useState( initialValues.CompositeToys ); const [CompositeToysLoading, setCompositeToysLoading] = React.useState(false); const [CompositeToysRecords, setCompositeToysRecords] = React.useState([]); const [CompositeVets, setCompositeVets] = React.useState( initialValues.CompositeVets ); const [CompositeVetsLoading, setCompositeVetsLoading] = React.useState(false); const [CompositeVetsRecords, setCompositeVetsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setDescription(initialValues.description); setCompositeBowl(initialValues.CompositeBowl); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); setCompositeOwner(initialValues.CompositeOwner); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); setCompositeToys(initialValues.CompositeToys); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); setCompositeVets(initialValues.CompositeVets); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); setErrors({}); }; const [ currentCompositeBowlDisplayValue, setCurrentCompositeBowlDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeBowlValue, setCurrentCompositeBowlValue] = React.useState(undefined); const CompositeBowlRef = React.createRef(); const [ currentCompositeOwnerDisplayValue, setCurrentCompositeOwnerDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeOwnerValue, setCurrentCompositeOwnerValue] = React.useState(undefined); const CompositeOwnerRef = React.createRef(); const [ currentCompositeToysDisplayValue, setCurrentCompositeToysDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeToysValue, setCurrentCompositeToysValue] = React.useState(undefined); const CompositeToysRef = React.createRef(); const [ currentCompositeVetsDisplayValue, setCurrentCompositeVetsDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeVetsValue, setCurrentCompositeVetsValue] = React.useState(undefined); const CompositeVetsRef = React.createRef(); const getIDValue = { CompositeBowl: (r) => JSON.stringify({ shape: r?.shape, size: r?.size }), CompositeOwner: (r) => JSON.stringify({ lastName: r?.lastName, firstName: r?.firstName }), CompositeToys: (r) => JSON.stringify({ kind: r?.kind, color: r?.color }), CompositeVets: (r) => JSON.stringify({ specialty: r?.specialty, city: r?.city }), }; const CompositeBowlIdSet = new Set( Array.isArray(CompositeBowl) ? CompositeBowl.map((r) => getIDValue.CompositeBowl?.(r)) : getIDValue.CompositeBowl?.(CompositeBowl) ); const CompositeOwnerIdSet = new Set( Array.isArray(CompositeOwner) ? CompositeOwner.map((r) => getIDValue.CompositeOwner?.(r)) : getIDValue.CompositeOwner?.(CompositeOwner) ); const CompositeToysIdSet = new Set( Array.isArray(CompositeToys) ? CompositeToys.map((r) => getIDValue.CompositeToys?.(r)) : getIDValue.CompositeToys?.(CompositeToys) ); const CompositeVetsIdSet = new Set( Array.isArray(CompositeVets) ? CompositeVets.map((r) => getIDValue.CompositeVets?.(r)) : getIDValue.CompositeVets?.(CompositeVets) ); const getDisplayValue = { CompositeBowl: (r) => \`\${r?.shape}\${\\"-\\"}\${r?.size}\`, CompositeOwner: (r) => \`\${r?.lastName}\${\\"-\\"}\${r?.firstName}\`, CompositeToys: (r) => \`\${r?.kind}\${\\"-\\"}\${r?.color}\`, CompositeVets: (r) => \`\${r?.specialty}\${\\"-\\"}\${r?.city}\`, }; const validations = { name: [{ type: \\"Required\\" }], description: [{ type: \\"Required\\" }], CompositeBowl: [], CompositeOwner: [], CompositeToys: [], CompositeVets: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchCompositeBowlRecords = async (value) => { setCompositeBowlLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ shape: { contains: value } }, { size: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeBowls, variables, }) )?.data?.listCompositeBowls?.items; var loaded = result.filter( (item) => !CompositeBowlIdSet.has(getIDValue.CompositeBowl?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeBowlRecords(newOptions.slice(0, autocompleteLength)); setCompositeBowlLoading(false); }; const fetchCompositeOwnerRecords = async (value) => { setCompositeOwnerLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [ { lastName: { contains: value } }, { firstName: { contains: value } }, ], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeOwners, variables, }) )?.data?.listCompositeOwners?.items; var loaded = result.filter( (item) => !CompositeOwnerIdSet.has(getIDValue.CompositeOwner?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeOwnerRecords(newOptions.slice(0, autocompleteLength)); setCompositeOwnerLoading(false); }; const fetchCompositeToysRecords = async (value) => { setCompositeToysLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ kind: { contains: value } }, { color: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeToys, variables, }) )?.data?.listCompositeToys?.items; var loaded = result.filter( (item) => !CompositeToysIdSet.has(getIDValue.CompositeToys?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeToysRecords(newOptions.slice(0, autocompleteLength)); setCompositeToysLoading(false); }; const fetchCompositeVetsRecords = async (value) => { setCompositeVetsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [ { specialty: { contains: value } }, { city: { contains: value } }, ], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listCompositeVets, variables, }) )?.data?.listCompositeVets?.items; var loaded = result.filter( (item) => !CompositeVetsIdSet.has(getIDValue.CompositeVets?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCompositeVetsRecords(newOptions.slice(0, autocompleteLength)); setCompositeVetsLoading(false); }; React.useEffect(() => { fetchCompositeBowlRecords(\\"\\"); fetchCompositeOwnerRecords(\\"\\"); fetchCompositeToysRecords(\\"\\"); fetchCompositeVetsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { name: modelFields.name, description: modelFields.description, CompositeBowl: modelFields.CompositeBowl, CompositeOwner: modelFields.CompositeOwner, }; const compositeDog = await API.graphql({ query: createCompositeDog, variables: { input: { ...modelFieldsToSave, }, }, }); const promises = []; const compositeOwnerToLink = modelFields.CompositeOwner; if (compositeOwnerToLink) { promises.push( API.graphql({ query: updateCompositeOwner, variables: { input: { ...CompositeOwner, CompositeDog: compositeDog, }, }, }) ); const compositeDogToUnlink = await compositeOwnerToLink.CompositeDog; if (compositeDogToUnlink) { promises.push( API.graphql({ query: updateCompositeDog, variables: { input: { ...compositeDogToUnlink, CompositeOwner: undefined, compositeDogCompositeOwnerLastName: undefined, compositeDogCompositeOwnerFirstName: undefined, }, }, }) ); } } promises.push( ...CompositeToys.reduce((promises, original) => { promises.push( API.graphql({ query: updateCompositeDog, variables: { input: { ...original, compositeDogCompositeToysName: compositeDog.name, compositeDogCompositeToysDescription: compositeDog.description, }, }, }) ); return promises; }, []) ); promises.push( ...CompositeVets.reduce((promises, compositeVet) => { promises.push( API.graphql({ query: createCompositeDogCompositeVet, variables: { input: { compositeDog, compositeVet, }, }, }) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCompositeDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"Description\\" isRequired={true} isReadOnly={false} value={description} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, description: value, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.description ?? value; } if (errors.description?.hasError) { runValidationTasks(\\"description\\", value); } setDescription(value); }} onBlur={() => runValidationTasks(\\"description\\", description)} errorMessage={errors.description?.errorMessage} hasError={errors.description?.hasError} {...getOverrideProps(overrides, \\"description\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl: value, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeBowl ?? value; } setCompositeBowl(value); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeBowlValue} label={\\"Composite bowl\\"} items={CompositeBowl ? [CompositeBowl] : []} hasError={errors?.CompositeBowl?.hasError} errorMessage={errors?.CompositeBowl?.errorMessage} getBadgeText={getDisplayValue.CompositeBowl} setFieldValue={(model) => { setCurrentCompositeBowlDisplayValue( model ? getDisplayValue.CompositeBowl(model) : \\"\\" ); setCurrentCompositeBowlValue(model); }} inputFieldRef={CompositeBowlRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite bowl\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeBowl\\" value={currentCompositeBowlDisplayValue} options={CompositeBowlRecords.filter( (r) => !CompositeBowlIdSet.has(getIDValue.CompositeBowl?.(r)) ).map((r) => ({ id: getIDValue.CompositeBowl?.(r), label: getDisplayValue.CompositeBowl?.(r), }))} isLoading={CompositeBowlLoading} onSelect={({ id, label }) => { setCurrentCompositeBowlValue( CompositeBowlRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeBowlDisplayValue(label); runValidationTasks(\\"CompositeBowl\\", label); }} onClear={() => { setCurrentCompositeBowlDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeBowlRecords(value); if (errors.CompositeBowl?.hasError) { runValidationTasks(\\"CompositeBowl\\", value); } setCurrentCompositeBowlDisplayValue(value); setCurrentCompositeBowlValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeBowl\\", currentCompositeBowlDisplayValue ) } errorMessage={errors.CompositeBowl?.errorMessage} hasError={errors.CompositeBowl?.hasError} ref={CompositeBowlRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeBowl\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner: value, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeOwner ?? value; } setCompositeOwner(value); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeOwnerValue} label={\\"Composite owner\\"} items={CompositeOwner ? [CompositeOwner] : []} hasError={errors?.CompositeOwner?.hasError} errorMessage={errors?.CompositeOwner?.errorMessage} getBadgeText={getDisplayValue.CompositeOwner} setFieldValue={(model) => { setCurrentCompositeOwnerDisplayValue( model ? getDisplayValue.CompositeOwner(model) : \\"\\" ); setCurrentCompositeOwnerValue(model); }} inputFieldRef={CompositeOwnerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite owner\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeOwner\\" value={currentCompositeOwnerDisplayValue} options={CompositeOwnerRecords.filter( (r) => !CompositeOwnerIdSet.has(getIDValue.CompositeOwner?.(r)) ).map((r) => ({ id: getIDValue.CompositeOwner?.(r), label: getDisplayValue.CompositeOwner?.(r), }))} isLoading={CompositeOwnerLoading} onSelect={({ id, label }) => { setCurrentCompositeOwnerValue( CompositeOwnerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeOwnerDisplayValue(label); runValidationTasks(\\"CompositeOwner\\", label); }} onClear={() => { setCurrentCompositeOwnerDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeOwnerRecords(value); if (errors.CompositeOwner?.hasError) { runValidationTasks(\\"CompositeOwner\\", value); } setCurrentCompositeOwnerDisplayValue(value); setCurrentCompositeOwnerValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeOwner\\", currentCompositeOwnerDisplayValue ) } errorMessage={errors.CompositeOwner?.errorMessage} hasError={errors.CompositeOwner?.hasError} ref={CompositeOwnerRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeOwner\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys: values, CompositeVets, }; const result = onChange(modelFields); values = result?.CompositeToys ?? values; } setCompositeToys(values); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeToysValue} label={\\"Composite toys\\"} items={CompositeToys} hasError={errors?.CompositeToys?.hasError} errorMessage={errors?.CompositeToys?.errorMessage} getBadgeText={getDisplayValue.CompositeToys} setFieldValue={(model) => { setCurrentCompositeToysDisplayValue( model ? getDisplayValue.CompositeToys(model) : \\"\\" ); setCurrentCompositeToysValue(model); }} inputFieldRef={CompositeToysRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite toys\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeToy\\" value={currentCompositeToysDisplayValue} options={CompositeToysRecords.filter( (r) => !CompositeToysIdSet.has(getIDValue.CompositeToys?.(r)) ).map((r) => ({ id: getIDValue.CompositeToys?.(r), label: getDisplayValue.CompositeToys?.(r), }))} isLoading={CompositeToysLoading} onSelect={({ id, label }) => { setCurrentCompositeToysValue( CompositeToysRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeToysDisplayValue(label); runValidationTasks(\\"CompositeToys\\", label); }} onClear={() => { setCurrentCompositeToysDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeToysRecords(value); if (errors.CompositeToys?.hasError) { runValidationTasks(\\"CompositeToys\\", value); } setCurrentCompositeToysDisplayValue(value); setCurrentCompositeToysValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeToys\\", currentCompositeToysDisplayValue ) } errorMessage={errors.CompositeToys?.errorMessage} hasError={errors.CompositeToys?.hasError} ref={CompositeToysRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeToys\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets: values, }; const result = onChange(modelFields); values = result?.CompositeVets ?? values; } setCompositeVets(values); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeVetsValue} label={\\"Composite vets\\"} items={CompositeVets} hasError={errors?.CompositeVets?.hasError} errorMessage={errors?.CompositeVets?.errorMessage} getBadgeText={getDisplayValue.CompositeVets} setFieldValue={(model) => { setCurrentCompositeVetsDisplayValue( model ? getDisplayValue.CompositeVets(model) : \\"\\" ); setCurrentCompositeVetsValue(model); }} inputFieldRef={CompositeVetsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite vets\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeVet\\" value={currentCompositeVetsDisplayValue} options={CompositeVetsRecords.map((r) => ({ id: getIDValue.CompositeVets?.(r), label: getDisplayValue.CompositeVets?.(r), }))} isLoading={CompositeVetsLoading} onSelect={({ id, label }) => { setCurrentCompositeVetsValue( CompositeVetsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeVetsDisplayValue(label); runValidationTasks(\\"CompositeVets\\", label); }} onClear={() => { setCurrentCompositeVetsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCompositeVetsRecords(value); if (errors.CompositeVets?.hasError) { runValidationTasks(\\"CompositeVets\\", value); } setCurrentCompositeVetsDisplayValue(value); setCurrentCompositeVetsValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeVets\\", currentCompositeVetsDisplayValue ) } errorMessage={errors.CompositeVets?.errorMessage} hasError={errors.CompositeVets?.hasError} ref={CompositeVetsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeVets\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for model with composite keys 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeBowl, CompositeOwner, CompositeToy, CompositeVet } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCompositeDogFormInputValues = { name?: string; description?: string; CompositeBowl?: CompositeBowl; CompositeOwner?: CompositeOwner; CompositeToys?: CompositeToy[]; CompositeVets?: CompositeVet[]; }; export declare type CreateCompositeDogFormValidationValues = { name?: ValidationFunction<string>; description?: ValidationFunction<string>; CompositeBowl?: ValidationFunction<CompositeBowl>; CompositeOwner?: ValidationFunction<CompositeOwner>; CompositeToys?: ValidationFunction<CompositeToy>; CompositeVets?: ValidationFunction<CompositeVet>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCompositeDogFormOverridesProps = { CreateCompositeDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; description?: PrimitiveOverrideProps<TextFieldProps>; CompositeBowl?: PrimitiveOverrideProps<AutocompleteProps>; CompositeOwner?: PrimitiveOverrideProps<AutocompleteProps>; CompositeToys?: PrimitiveOverrideProps<AutocompleteProps>; CompositeVets?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCompositeDogFormProps = React.PropsWithChildren<{ overrides?: CreateCompositeDogFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCompositeDogFormInputValues) => CreateCompositeDogFormInputValues; onSuccess?: (fields: CreateCompositeDogFormInputValues) => void; onError?: (fields: CreateCompositeDogFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCompositeDogFormInputValues) => CreateCompositeDogFormInputValues; onValidate?: CreateCompositeDogFormValidationValues; } & React.CSSProperties>; export default function CreateCompositeDogForm(props: CreateCompositeDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for parent of 1:m-belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listComments } from \\"../graphql/queries\\"; import { createPost, updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreatePostForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", comments: [], }; const [name, setName] = React.useState(initialValues.name); const [comments, setComments] = React.useState(initialValues.comments); const [commentsLoading, setCommentsLoading] = React.useState(false); const [commentsRecords, setCommentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setComments(initialValues.comments); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = React.useState(\\"\\"); const [currentCommentsValue, setCurrentCommentsValue] = React.useState(undefined); const commentsRef = React.createRef(); const getIDValue = { comments: (r) => JSON.stringify({ id: r?.id }), }; const commentsIdSet = new Set( Array.isArray(comments) ? comments.map((r) => getIDValue.comments?.(r)) : getIDValue.comments?.(comments) ); const getDisplayValue = { comments: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], comments: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchCommentsRecords = async (value) => { setCommentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listComments, variables, }) )?.data?.listComments?.items; var loaded = result.filter( (item) => !commentsIdSet.has(getIDValue.comments?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCommentsRecords(newOptions.slice(0, autocompleteLength)); setCommentsLoading(false); }; React.useEffect(() => { fetchCommentsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, comments, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { name: modelFields.name, }; const post = await API.graphql({ query: createPost, variables: { input: { ...modelFieldsToSave, }, }, }); const promises = []; promises.push( ...comments.reduce((promises, original) => { promises.push( API.graphql({ query: updatePost, variables: { input: { ...original, postCommentsId: post.id, post: post, }, }, }) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreatePostForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, comments, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, comments: values, }; const result = onChange(modelFields); values = result?.comments ?? values; } setComments(values); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); }} currentFieldValue={currentCommentsValue} label={\\"Comments\\"} items={comments} hasError={errors?.comments?.hasError} errorMessage={errors?.comments?.errorMessage} getBadgeText={getDisplayValue.comments} setFieldValue={(model) => { setCurrentCommentsDisplayValue( model ? getDisplayValue.comments(model) : \\"\\" ); setCurrentCommentsValue(model); }} inputFieldRef={commentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Comments\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Comment\\" value={currentCommentsDisplayValue} options={commentsRecords.map((r) => ({ id: getIDValue.comments?.(r), label: getDisplayValue.comments?.(r), }))} isLoading={commentsLoading} onSelect={({ id, label }) => { setCurrentCommentsValue( commentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCommentsDisplayValue(label); runValidationTasks(\\"comments\\", label); }} onClear={() => { setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCommentsRecords(value); if (errors.comments?.hasError) { runValidationTasks(\\"comments\\", value); } setCurrentCommentsDisplayValue(value); setCurrentCommentsValue(undefined); }} onBlur={() => runValidationTasks(\\"comments\\", currentCommentsDisplayValue) } errorMessage={errors.comments?.errorMessage} hasError={errors.comments?.hasError} ref={commentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"comments\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render a create form for parent of 1:m-belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Comment } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreatePostFormInputValues = { name?: string; comments?: Comment[]; }; export declare type CreatePostFormValidationValues = { name?: ValidationFunction<string>; comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreatePostFormOverridesProps = { CreatePostFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreatePostFormProps = React.PropsWithChildren<{ overrides?: CreatePostFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreatePostFormInputValues) => CreatePostFormInputValues; onSuccess?: (fields: CreatePostFormInputValues) => void; onError?: (fields: CreatePostFormInputValues, errorMessage: string) => void; onChange?: (fields: CreatePostFormInputValues) => CreatePostFormInputValues; onValidate?: CreatePostFormValidationValues; } & React.CSSProperties>; export default function CreatePostForm(props: CreatePostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render a update form for parent of 1:m-belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getPost, listComments } from \\"../graphql/queries\\"; import { updateComment, updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdatePostForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", comments: [], }; const [name, setName] = React.useState(initialValues.name); const [comments, setComments] = React.useState(initialValues.comments); const [commentsLoading, setCommentsLoading] = React.useState(false); const [commentsRecords, setCommentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord ? { ...initialValues, ...postRecord, comments: linkedComments } : initialValues; setName(cleanValues.name); setComments(cleanValues.comments ?? []); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); const [linkedComments, setLinkedComments] = React.useState([]); const canUnlinkComments = true; React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getPost, variables: { id: idProp }, }) )?.data?.getPost : postModelProp; const linkedComments = record?.Comment?.items ?? []; setLinkedComments(linkedComments); setPostRecord(record); }; queryData(); }, [idProp, postModelProp]); React.useEffect(resetStateValues, [postRecord, linkedComments]); const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = React.useState(\\"\\"); const [currentCommentsValue, setCurrentCommentsValue] = React.useState(undefined); const commentsRef = React.createRef(); const getIDValue = { comments: (r) => JSON.stringify({ id: r?.id }), }; const commentsIdSet = new Set( Array.isArray(comments) ? comments.map((r) => getIDValue.comments?.(r)) : getIDValue.comments?.(comments) ); const getDisplayValue = { comments: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], comments: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchCommentsRecords = async (value) => { setCommentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listComments, variables, }) )?.data?.listComments?.items; var loaded = result.filter( (item) => !commentsIdSet.has(getIDValue.comments?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setCommentsRecords(newOptions.slice(0, autocompleteLength)); setCommentsLoading(false); }; React.useEffect(() => { fetchCommentsRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, comments, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const commentsToLink = []; const commentsToUnLink = []; const commentsSet = new Set(); const linkedCommentsSet = new Set(); comments.forEach((r) => commentsSet.add(getIDValue.comments?.(r))); linkedComments.forEach((r) => linkedCommentsSet.add(getIDValue.comments?.(r)) ); linkedComments.forEach((r) => { if (!commentsSet.has(getIDValue.comments?.(r))) { commentsToUnLink.push(r); } }); comments.forEach((r) => { if (!linkedCommentsSet.has(getIDValue.comments?.(r))) { commentsToLink.push(r); } }); commentsToUnLink.forEach((original) => { if (!canUnlinkComments) { throw Error( \`Comment \${original.id} cannot be unlinked from Post because postCommentsId is a required field.\` ); } promises.push( API.graphql({ query: updateComment, variables: { input: { id: original.id, postCommentsId: null, }, }, }) ); }); commentsToLink.forEach((original) => { promises.push( API.graphql({ query: updateComment, variables: { input: { id: original.id, postCommentsId: postRecord.id, }, }, }) ); }); const modelFieldsToSave = { name: modelFields.name, }; promises.push( API.graphql({ query: updatePost, variables: { input: { id: postRecord.id, ...modelFieldsToSave, }, }, }) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdatePostForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, comments, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, comments: values, }; const result = onChange(modelFields); values = result?.comments ?? values; } setComments(values); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); }} currentFieldValue={currentCommentsValue} label={\\"Comments\\"} items={comments} hasError={errors?.comments?.hasError} errorMessage={errors?.comments?.errorMessage} getBadgeText={getDisplayValue.comments} setFieldValue={(model) => { setCurrentCommentsDisplayValue( model ? getDisplayValue.comments(model) : \\"\\" ); setCurrentCommentsValue(model); }} inputFieldRef={commentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Comments\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Comment\\" value={currentCommentsDisplayValue} options={commentsRecords.map((r) => ({ id: getIDValue.comments?.(r), label: getDisplayValue.comments?.(r), }))} isLoading={commentsLoading} onSelect={({ id, label }) => { setCurrentCommentsValue( commentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCommentsDisplayValue(label); runValidationTasks(\\"comments\\", label); }} onClear={() => { setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchCommentsRecords(value); if (errors.comments?.hasError) { runValidationTasks(\\"comments\\", value); } setCurrentCommentsDisplayValue(value); setCurrentCommentsValue(undefined); }} onBlur={() => runValidationTasks(\\"comments\\", currentCommentsDisplayValue) } errorMessage={errors.comments?.errorMessage} hasError={errors.comments?.hasError} ref={commentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"comments\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render a update form for parent of 1:m-belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdatePostFormInputValues = { name?: string; comments?: Comment[]; }; export declare type UpdatePostFormValidationValues = { name?: ValidationFunction<string>; comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdatePostFormOverridesProps = { UpdatePostFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdatePostFormProps = React.PropsWithChildren<{ overrides?: UpdatePostFormOverridesProps | undefined | null; } & { id?: string; post?: Post; onSubmit?: (fields: UpdatePostFormInputValues) => UpdatePostFormInputValues; onSuccess?: (fields: UpdatePostFormInputValues) => void; onError?: (fields: UpdatePostFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdatePostFormInputValues) => UpdatePostFormInputValues; onValidate?: UpdatePostFormValidationValues; } & React.CSSProperties>; export default function UpdatePostForm(props: UpdatePostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render thrown error for required parent field 1:1 relationships - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Owner } from \\"../API\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listOwners } from \\"../graphql/queries\\"; import { createDog, updateOwner } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateDogForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", owner: undefined, }; const [name, setName] = React.useState(initialValues.name); const [owner, setOwner] = React.useState(initialValues.owner); const [ownerLoading, setOwnerLoading] = React.useState(false); const [ownerRecords, setOwnerRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setOwner(initialValues.owner); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); setErrors({}); }; const [currentOwnerDisplayValue, setCurrentOwnerDisplayValue] = React.useState(\\"\\"); const [currentOwnerValue, setCurrentOwnerValue] = React.useState(undefined); const ownerRef = React.createRef(); const getIDValue = { owner: (r) => JSON.stringify({ id: r?.id }), }; const ownerIdSet = new Set( Array.isArray(owner) ? owner.map((r) => getIDValue.owner?.(r)) : getIDValue.owner?.(owner) ); const getDisplayValue = { owner: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], owner: [{ type: \\"Required\\", validationMessage: \\"Owner is required.\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchOwnerRecords = async (value) => { setOwnerLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listOwners, variables, }) )?.data?.listOwners?.items; var loaded = result.filter( (item) => !ownerIdSet.has(getIDValue.owner?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setOwnerRecords(newOptions.slice(0, autocompleteLength)); setOwnerLoading(false); }; React.useEffect(() => { fetchOwnerRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, owner, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const dog = await API.graphql({ query: createDog, variables: { input: { ...modelFields, }, }, }); const promises = []; const ownerToLink = modelFields.owner; if (ownerToLink) { promises.push( API.graphql({ query: updateOwner, variables: { input: { ...Owner, Dog: dog, }, }, }) ); const dogToUnlink = await ownerToLink.Dog; if (dogToUnlink) { if (JSON.stringify(dogToUnlink) !== JSON.stringify(dog)) { throw Error( \`Owner \${ownerToLink.id} cannot be linked to Dog because it is already linked to another Dog.\` ); } } } await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, owner, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, owner: value, }; const result = onChange(modelFields); value = result?.owner ?? value; } setOwner(value); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentOwnerValue} label={\\"Owner\\"} items={owner ? [owner] : []} hasError={errors?.owner?.hasError} errorMessage={errors?.owner?.errorMessage} getBadgeText={getDisplayValue.owner} setFieldValue={(model) => { setCurrentOwnerDisplayValue( model ? getDisplayValue.owner(model) : \\"\\" ); setCurrentOwnerValue(model); }} inputFieldRef={ownerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Owner\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Owner\\" value={currentOwnerDisplayValue} options={ownerRecords .filter((r) => !ownerIdSet.has(getIDValue.owner?.(r))) .map((r) => ({ id: getIDValue.owner?.(r), label: getDisplayValue.owner?.(r), }))} isLoading={ownerLoading} onSelect={({ id, label }) => { setCurrentOwnerValue( ownerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentOwnerDisplayValue(label); runValidationTasks(\\"owner\\", label); }} onClear={() => { setCurrentOwnerDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchOwnerRecords(value); if (errors.owner?.hasError) { runValidationTasks(\\"owner\\", value); } setCurrentOwnerDisplayValue(value); setCurrentOwnerValue(undefined); }} onBlur={() => runValidationTasks(\\"owner\\", currentOwnerDisplayValue)} errorMessage={errors.owner?.errorMessage} hasError={errors.owner?.hasError} ref={ownerRef} labelHidden={true} {...getOverrideProps(overrides, \\"owner\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render thrown error for required parent field 1:1 relationships - Create 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Owner } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateDogFormInputValues = { name?: string; owner?: Owner; }; export declare type CreateDogFormValidationValues = { name?: ValidationFunction<string>; owner?: ValidationFunction<Owner>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateDogFormOverridesProps = { CreateDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; owner?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateDogFormProps = React.PropsWithChildren<{ overrides?: CreateDogFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateDogFormInputValues) => CreateDogFormInputValues; onSuccess?: (fields: CreateDogFormInputValues) => void; onError?: (fields: CreateDogFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateDogFormInputValues) => CreateDogFormInputValues; onValidate?: CreateDogFormValidationValues; } & React.CSSProperties>; export default function CreateDogForm(props: CreateDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should render thrown error for required related field 1:1 relationships - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { listDogs } from \\"../graphql/queries\\"; import { createOwner, updateDog, updateOwner } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateOwnerForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Dog: undefined, }; const [name, setName] = React.useState(initialValues.name); const [Dog, setDog] = React.useState(initialValues.Dog); const [DogLoading, setDogLoading] = React.useState(false); const [DogRecords, setDogRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setDog(initialValues.Dog); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); const [currentDogValue, setCurrentDogValue] = React.useState(undefined); const DogRef = React.createRef(); const getIDValue = { Dog: (r) => JSON.stringify({ id: r?.id }), }; const DogIdSet = new Set( Array.isArray(Dog) ? Dog.map((r) => getIDValue.Dog?.(r)) : getIDValue.Dog?.(Dog) ); const getDisplayValue = { Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], Dog: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchDogRecords = async (value) => { setDogLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listDogs, variables, }) )?.data?.listDogs?.items; var loaded = result.filter( (item) => !DogIdSet.has(getIDValue.Dog?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } setDogRecords(newOptions.slice(0, autocompleteLength)); setDogLoading(false); }; React.useEffect(() => { fetchDogRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const owner = await API.graphql({ query: createOwner, variables: { input: { ...modelFields, }, }, }); const promises = []; const dogToLink = modelFields.Dog; if (dogToLink) { promises.push( API.graphql({ query: updateDog, variables: { input: { ...Dog, owner: owner, }, }, }) ); const ownerToUnlink = await dogToLink.owner; if (ownerToUnlink) { promises.push( API.graphql({ query: updateOwner, variables: { input: { ...ownerToUnlink, Dog: undefined, ownerDogId: undefined, }, }, }) ); } } await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateOwnerForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Dog, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, Dog: value, }; const result = onChange(modelFields); value = result?.Dog ?? value; } setDog(value); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); }} currentFieldValue={currentDogValue} label={\\"Dog\\"} items={Dog ? [Dog] : []} hasError={errors?.Dog?.hasError} errorMessage={errors?.Dog?.errorMessage} getBadgeText={getDisplayValue.Dog} setFieldValue={(model) => { setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); setCurrentDogValue(model); }} inputFieldRef={DogRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Dog\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Dog\\" value={currentDogDisplayValue} options={DogRecords.filter( (r) => !DogIdSet.has(getIDValue.Dog?.(r)) ).map((r) => ({ id: getIDValue.Dog?.(r), label: getDisplayValue.Dog?.(r), }))} isLoading={DogLoading} onSelect={({ id, label }) => { setCurrentDogValue( DogRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentDogDisplayValue(label); runValidationTasks(\\"Dog\\", label); }} onClear={() => { setCurrentDogDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; fetchDogRecords(value); if (errors.Dog?.hasError) { runValidationTasks(\\"Dog\\", value); } setCurrentDogDisplayValue(value); setCurrentDogValue(undefined); }} onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} errorMessage={errors.Dog?.errorMessage} hasError={errors.Dog?.hasError} ref={DogRef} labelHidden={true} {...getOverrideProps(overrides, \\"Dog\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should render thrown error for required related field 1:1 relationships - Create 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Dog } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateOwnerFormInputValues = { name?: string; Dog?: Dog; }; export declare type CreateOwnerFormValidationValues = { name?: ValidationFunction<string>; Dog?: ValidationFunction<Dog>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateOwnerFormOverridesProps = { CreateOwnerFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Dog?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateOwnerFormProps = React.PropsWithChildren<{ overrides?: CreateOwnerFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onSuccess?: (fields: CreateOwnerFormInputValues) => void; onError?: (fields: CreateOwnerFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onValidate?: CreateOwnerFormValidationValues; } & React.CSSProperties>; export default function CreateOwnerForm(props: CreateOwnerFormProps): React.ReactElement; " `; exports[`amplify form renderer tests GraphQL form tests should treat relationship as bidirectional without belongsTo 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; import { getComment, getPost, listPosts } from \\"../graphql/queries\\"; import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CommentUpdateForm(props) { const { id: idProp, comment: commentModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { content: \\"\\", postID: undefined, }; const [content, setContent] = React.useState(initialValues.content); const [postID, setPostID] = React.useState(initialValues.postID); const [postIDLoading, setPostIDLoading] = React.useState(false); const [postIDRecords, setPostIDRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = commentRecord ? { ...initialValues, ...commentRecord, postID } : initialValues; setContent(cleanValues.content); setPostID(cleanValues.postID); setCurrentPostIDValue(undefined); setCurrentPostIDDisplayValue(\\"\\"); setErrors({}); }; const [commentRecord, setCommentRecord] = React.useState(commentModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ query: getComment, variables: { id: idProp }, }) )?.data?.getComment : commentModelProp; const postIDRecord = record ? record.postID : undefined; const postRecord = postIDRecord ? ( await API.graphql({ query: getPost, variables: { id: postIDRecord }, }) )?.data?.getPost : undefined; setPostID(postIDRecord); setPostIDRecords([postRecord]); setCommentRecord(record); }; queryData(); }, [idProp, commentModelProp]); React.useEffect(resetStateValues, [commentRecord, postID]); const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); const postIDRef = React.createRef(); const getDisplayValue = { postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { content: [], postID: [{ type: \\"Required\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const fetchPostIDRecords = async (value) => { setPostIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ query: listPosts, variables, }) )?.data?.listPosts?.items; var loaded = result.filter((item) => postID !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } setPostIDRecords(newOptions.slice(0, autocompleteLength)); setPostIDLoading(false); }; React.useEffect(() => { fetchPostIDRecords(\\"\\"); }, []); return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { content, postID, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await API.graphql({ query: updateComment, variables: { input: { id: commentRecord.id, ...modelFields, }, }, }); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CommentUpdateForm\\")} {...rest} > <TextField label=\\"Content\\" isRequired={false} isReadOnly={false} value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { content: value, postID, }; const result = onChange(modelFields); value = result?.content ?? value; } if (errors.content?.hasError) { runValidationTasks(\\"content\\", value); } setContent(value); }} onBlur={() => runValidationTasks(\\"content\\", content)} errorMessage={errors.content?.errorMessage} hasError={errors.content?.hasError} {...getOverrideProps(overrides, \\"content\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { content, postID: value, }; const result = onChange(modelFields); value = result?.postID ?? value; } setPostID(value); setCurrentPostIDValue(undefined); }} currentFieldValue={currentPostIDValue} label={\\"Post id\\"} items={postID ? [postID] : []} hasError={errors?.postID?.hasError} errorMessage={errors?.postID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.postID(postIDRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentPostIDDisplayValue( value ? getDisplayValue.postID( postIDRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentPostIDValue(value); }} inputFieldRef={postIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostIDDisplayValue} options={postIDRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.postID?.(r), }))} isLoading={postIDLoading} onSelect={({ id, label }) => { setCurrentPostIDValue(id); setCurrentPostIDDisplayValue(label); runValidationTasks(\\"postID\\", label); }} onClear={() => { setCurrentPostIDDisplayValue(\\"\\"); }} defaultValue={postID} onChange={(e) => { let { value } = e.target; fetchPostIDRecords(value); if (errors.postID?.hasError) { runValidationTasks(\\"postID\\", value); } setCurrentPostIDDisplayValue(value); setCurrentPostIDValue(undefined); }} onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} errorMessage={errors.postID?.errorMessage} hasError={errors.postID?.hasError} ref={postIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"postID\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || commentModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || commentModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests GraphQL form tests should treat relationship as bidirectional without belongsTo 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Comment } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CommentUpdateFormInputValues = { content?: string; postID?: string; }; export declare type CommentUpdateFormValidationValues = { content?: ValidationFunction<string>; postID?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CommentUpdateFormOverridesProps = { CommentUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; content?: PrimitiveOverrideProps<TextFieldProps>; postID?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CommentUpdateFormProps = React.PropsWithChildren<{ overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; comment?: Comment; onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onSuccess?: (fields: CommentUpdateFormInputValues) => void; onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; onChange?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onValidate?: CommentUpdateFormValidationValues; } & React.CSSProperties>; export default function CommentUpdateForm(props: CommentUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests NoApi form tests should render custom data form successfully with no configured API 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CustomDataForm(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"John Doe\\", email: [\\"johndoe@amplify.com\\"], phone: [\\"+1-401-152-6995\\"], city: undefined, }; const [name, setName] = React.useState(initialValues.name); const [email, setEmail] = React.useState(initialValues.email); const [phone, setPhone] = React.useState(initialValues.phone); const [city, setCity] = React.useState(initialValues.city); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setEmail(initialValues.email); setCurrentEmailValue(\\"\\"); setPhone(initialValues.phone); setCurrentPhoneValue(\\"\\"); setCity(initialValues.city); setErrors({}); }; const [currentEmailValue, setCurrentEmailValue] = React.useState(\\"\\"); const emailRef = React.createRef(); const [currentPhoneValue, setCurrentPhoneValue] = React.useState(\\"\\"); const phoneRef = React.createRef(); const validations = { name: [{ type: \\"Required\\" }], email: [{ type: \\"Required\\" }], phone: [{ type: \\"Required\\" }, { type: \\"Phone\\" }], city: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, email, phone, city, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomDataForm\\")} {...rest} > <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>name</span> <span style={{ color: \\"red\\" }}>*</span> </span> } isRequired={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, email, phone, city, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, email: values, phone, city, }; const result = onChange(modelFields); values = result?.email ?? values; } setEmail(values); setCurrentEmailValue(\\"\\"); }} currentFieldValue={currentEmailValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>E-mail</span> <span style={{ color: \\"red\\" }}>*</span> </span> } items={email} hasError={errors?.email?.hasError} errorMessage={errors?.email?.errorMessage} setFieldValue={setCurrentEmailValue} inputFieldRef={emailRef} defaultFieldValue={\\"\\"} > <TextField label=\\"E-mail\\" isRequired={true} value={currentEmailValue} onChange={(e) => { let { value } = e.target; if (errors.email?.hasError) { runValidationTasks(\\"email\\", value); } setCurrentEmailValue(value); }} onBlur={() => runValidationTasks(\\"email\\", currentEmailValue)} errorMessage={errors.email?.errorMessage} hasError={errors.email?.hasError} ref={emailRef} labelHidden={true} {...getOverrideProps(overrides, \\"email\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, email, phone: values, city, }; const result = onChange(modelFields); values = result?.phone ?? values; } setPhone(values); setCurrentPhoneValue(\\"\\"); }} currentFieldValue={currentPhoneValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>phone</span> <span style={{ color: \\"red\\" }}>*</span> </span> } items={phone} hasError={errors?.phone?.hasError} errorMessage={errors?.phone?.errorMessage} setFieldValue={setCurrentPhoneValue} inputFieldRef={phoneRef} defaultFieldValue={\\"\\"} > <TextField label=\\"phone\\" isRequired={true} type=\\"tel\\" value={currentPhoneValue} onChange={(e) => { let { value } = e.target; if (errors.phone?.hasError) { runValidationTasks(\\"phone\\", value); } setCurrentPhoneValue(value); }} onBlur={() => runValidationTasks(\\"phone\\", currentPhoneValue)} errorMessage={errors.phone?.errorMessage} hasError={errors.phone?.hasError} ref={phoneRef} labelHidden={true} {...getOverrideProps(overrides, \\"phone\\")} ></TextField> </ArrayField> <Autocomplete label=\\"City\\" options={[ { id: \\"SF\\", label: \\"SF\\", }, { id: \\"LA\\", label: \\"LA\\", }, ]} onSelect={({ id, label }) => { setCity(id); runValidationTasks(\\"city\\", id); }} onClear={() => { setCity(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, phone, city: value, }; const result = onChange(modelFields); value = result?.city ?? value; } if (errors.city?.hasError) { runValidationTasks(\\"city\\", value); } setCity(value); }} onBlur={() => runValidationTasks(\\"city\\", city)} errorMessage={errors.city?.errorMessage} hasError={errors.city?.hasError} labelHidden={false} {...getOverrideProps(overrides, \\"city\\")} ></Autocomplete> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"empty\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"go back\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests NoApi form tests should render custom data form successfully with no configured API 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomDataFormInputValues = { name?: string; email?: string[]; phone?: string[]; city?: string; }; export declare type CustomDataFormValidationValues = { name?: ValidationFunction<string>; email?: ValidationFunction<string>; phone?: ValidationFunction<string>; city?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomDataFormOverridesProps = { CustomDataFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; email?: PrimitiveOverrideProps<TextFieldProps>; phone?: PrimitiveOverrideProps<TextFieldProps>; city?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CustomDataFormProps = React.PropsWithChildren<{ overrides?: CustomDataFormOverridesProps | undefined | null; } & { onSubmit: (fields: CustomDataFormInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues; onValidate?: CustomDataFormValidationValues; } & React.CSSProperties>; export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for child of 1:m relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeToy, CompositeDog } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCompositeToyForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { kind: \\"\\", color: \\"\\", compositeDogCompositeToysName: undefined, compositeDogCompositeToysDescription: undefined, }; const [kind, setKind] = React.useState(initialValues.kind); const [color, setColor] = React.useState(initialValues.color); const [compositeDogCompositeToysName, setCompositeDogCompositeToysName] = React.useState(initialValues.compositeDogCompositeToysName); const [ compositeDogCompositeToysDescription, setCompositeDogCompositeToysDescription, ] = React.useState(initialValues.compositeDogCompositeToysDescription); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setKind(initialValues.kind); setColor(initialValues.color); setCompositeDogCompositeToysName( initialValues.compositeDogCompositeToysName ); setCurrentCompositeDogCompositeToysNameValue(undefined); setCurrentCompositeDogCompositeToysNameDisplayValue(\\"\\"); setCompositeDogCompositeToysDescription( initialValues.compositeDogCompositeToysDescription ); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); setCurrentCompositeDogCompositeToysDescriptionDisplayValue(\\"\\"); setErrors({}); }; const [ currentCompositeDogCompositeToysNameDisplayValue, setCurrentCompositeDogCompositeToysNameDisplayValue, ] = React.useState(\\"\\"); const [ currentCompositeDogCompositeToysNameValue, setCurrentCompositeDogCompositeToysNameValue, ] = React.useState(undefined); const compositeDogCompositeToysNameRef = React.createRef(); const [ currentCompositeDogCompositeToysDescriptionDisplayValue, setCurrentCompositeDogCompositeToysDescriptionDisplayValue, ] = React.useState(\\"\\"); const [ currentCompositeDogCompositeToysDescriptionValue, setCurrentCompositeDogCompositeToysDescriptionValue, ] = React.useState(undefined); const compositeDogCompositeToysDescriptionRef = React.createRef(); const compositeDogRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeDog, }).items; const getDisplayValue = { compositeDogCompositeToysName: (r) => r?.name, compositeDogCompositeToysDescription: (r) => r?.description, }; const validations = { kind: [{ type: \\"Required\\" }], color: [{ type: \\"Required\\" }], compositeDogCompositeToysName: [], compositeDogCompositeToysDescription: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { kind, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new CompositeToy(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCompositeToyForm\\")} {...rest} > <TextField label=\\"Kind\\" isRequired={true} isReadOnly={false} value={kind} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { kind: value, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.kind ?? value; } if (errors.kind?.hasError) { runValidationTasks(\\"kind\\", value); } setKind(value); }} onBlur={() => runValidationTasks(\\"kind\\", kind)} errorMessage={errors.kind?.errorMessage} hasError={errors.kind?.hasError} {...getOverrideProps(overrides, \\"kind\\")} ></TextField> <TextField label=\\"Color\\" isRequired={true} isReadOnly={false} value={color} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { kind, color: value, compositeDogCompositeToysName, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.color ?? value; } if (errors.color?.hasError) { runValidationTasks(\\"color\\", value); } setColor(value); }} onBlur={() => runValidationTasks(\\"color\\", color)} errorMessage={errors.color?.errorMessage} hasError={errors.color?.hasError} {...getOverrideProps(overrides, \\"color\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { kind, color, compositeDogCompositeToysName: value, compositeDogCompositeToysDescription, }; const result = onChange(modelFields); value = result?.compositeDogCompositeToysName ?? value; } setCompositeDogCompositeToysName(value); setCurrentCompositeDogCompositeToysNameValue(undefined); }} currentFieldValue={currentCompositeDogCompositeToysNameValue} label={\\"Composite dog composite toys name\\"} items={ compositeDogCompositeToysName ? [compositeDogCompositeToysName] : [] } hasError={errors?.compositeDogCompositeToysName?.hasError} errorMessage={errors?.compositeDogCompositeToysName?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.compositeDogCompositeToysName( compositeDogRecords.find((r) => r.name === value) ) : \\"\\" } setFieldValue={(value) => { setCurrentCompositeDogCompositeToysNameDisplayValue( value ? getDisplayValue.compositeDogCompositeToysName( compositeDogRecords.find((r) => r.name === value) ) : \\"\\" ); setCurrentCompositeDogCompositeToysNameValue(value); }} inputFieldRef={compositeDogCompositeToysNameRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite dog composite toys name\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeDog\\" value={currentCompositeDogCompositeToysNameDisplayValue} options={compositeDogRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.name === r?.name) === i ) .map((r) => ({ id: r?.name, label: getDisplayValue.compositeDogCompositeToysName?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeDogCompositeToysNameValue(id); setCurrentCompositeDogCompositeToysNameDisplayValue(label); runValidationTasks(\\"compositeDogCompositeToysName\\", label); }} onClear={() => { setCurrentCompositeDogCompositeToysNameDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.compositeDogCompositeToysName?.hasError) { runValidationTasks(\\"compositeDogCompositeToysName\\", value); } setCurrentCompositeDogCompositeToysNameDisplayValue(value); setCurrentCompositeDogCompositeToysNameValue(undefined); }} onBlur={() => runValidationTasks( \\"compositeDogCompositeToysName\\", currentCompositeDogCompositeToysNameValue ) } errorMessage={errors.compositeDogCompositeToysName?.errorMessage} hasError={errors.compositeDogCompositeToysName?.hasError} ref={compositeDogCompositeToysNameRef} labelHidden={true} {...getOverrideProps(overrides, \\"compositeDogCompositeToysName\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { kind, color, compositeDogCompositeToysName, compositeDogCompositeToysDescription: value, }; const result = onChange(modelFields); value = result?.compositeDogCompositeToysDescription ?? value; } setCompositeDogCompositeToysDescription(value); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); }} currentFieldValue={currentCompositeDogCompositeToysDescriptionValue} label={\\"Composite dog composite toys description\\"} items={ compositeDogCompositeToysDescription ? [compositeDogCompositeToysDescription] : [] } hasError={errors?.compositeDogCompositeToysDescription?.hasError} errorMessage={ errors?.compositeDogCompositeToysDescription?.errorMessage } getBadgeText={(value) => value ? getDisplayValue.compositeDogCompositeToysDescription( compositeDogRecords.find((r) => r.description === value) ) : \\"\\" } setFieldValue={(value) => { setCurrentCompositeDogCompositeToysDescriptionDisplayValue( value ? getDisplayValue.compositeDogCompositeToysDescription( compositeDogRecords.find((r) => r.description === value) ) : \\"\\" ); setCurrentCompositeDogCompositeToysDescriptionValue(value); }} inputFieldRef={compositeDogCompositeToysDescriptionRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite dog composite toys description\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeDog\\" value={currentCompositeDogCompositeToysDescriptionDisplayValue} options={compositeDogRecords .filter( (r, i, arr) => arr.findIndex( (member) => member?.description === r?.description ) === i ) .map((r) => ({ id: r?.description, label: getDisplayValue.compositeDogCompositeToysDescription?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeDogCompositeToysDescriptionValue(id); setCurrentCompositeDogCompositeToysDescriptionDisplayValue(label); runValidationTasks(\\"compositeDogCompositeToysDescription\\", label); }} onClear={() => { setCurrentCompositeDogCompositeToysDescriptionDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.compositeDogCompositeToysDescription?.hasError) { runValidationTasks(\\"compositeDogCompositeToysDescription\\", value); } setCurrentCompositeDogCompositeToysDescriptionDisplayValue(value); setCurrentCompositeDogCompositeToysDescriptionValue(undefined); }} onBlur={() => runValidationTasks( \\"compositeDogCompositeToysDescription\\", currentCompositeDogCompositeToysDescriptionValue ) } errorMessage={ errors.compositeDogCompositeToysDescription?.errorMessage } hasError={errors.compositeDogCompositeToysDescription?.hasError} ref={compositeDogCompositeToysDescriptionRef} labelHidden={true} {...getOverrideProps( overrides, \\"compositeDogCompositeToysDescription\\" )} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for child of 1:m relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCompositeToyFormInputValues = { kind?: string; color?: string; compositeDogCompositeToysName?: string; compositeDogCompositeToysDescription?: string; }; export declare type CreateCompositeToyFormValidationValues = { kind?: ValidationFunction<string>; color?: ValidationFunction<string>; compositeDogCompositeToysName?: ValidationFunction<string>; compositeDogCompositeToysDescription?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCompositeToyFormOverridesProps = { CreateCompositeToyFormGrid?: PrimitiveOverrideProps<GridProps>; kind?: PrimitiveOverrideProps<TextFieldProps>; color?: PrimitiveOverrideProps<TextFieldProps>; compositeDogCompositeToysName?: PrimitiveOverrideProps<AutocompleteProps>; compositeDogCompositeToysDescription?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCompositeToyFormProps = React.PropsWithChildren<{ overrides?: CreateCompositeToyFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCompositeToyFormInputValues) => CreateCompositeToyFormInputValues; onSuccess?: (fields: CreateCompositeToyFormInputValues) => void; onError?: (fields: CreateCompositeToyFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCompositeToyFormInputValues) => CreateCompositeToyFormInputValues; onValidate?: CreateCompositeToyFormValidationValues; } & React.CSSProperties>; export default function CreateCompositeToyForm(props: CreateCompositeToyFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for child of 1:m-belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Comment, Post, User as User0, Org as Org0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCommentForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", post: undefined, User: undefined, Org: undefined, postCommentsId: undefined, }; const [name, setName] = React.useState(initialValues.name); const [post, setPost] = React.useState(initialValues.post); const [User, setUser] = React.useState(initialValues.User); const [Org, setOrg] = React.useState(initialValues.Org); const [postCommentsId, setPostCommentsId] = React.useState( initialValues.postCommentsId ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPost(initialValues.post); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); setUser(initialValues.User); setCurrentUserValue(undefined); setCurrentUserDisplayValue(\\"\\"); setOrg(initialValues.Org); setCurrentOrgValue(undefined); setCurrentOrgDisplayValue(\\"\\"); setPostCommentsId(initialValues.postCommentsId); setCurrentPostCommentsIdValue(undefined); setCurrentPostCommentsIdDisplayValue(\\"\\"); setErrors({}); }; const [currentPostDisplayValue, setCurrentPostDisplayValue] = React.useState(\\"\\"); const [currentPostValue, setCurrentPostValue] = React.useState(undefined); const postRef = React.createRef(); const [currentUserDisplayValue, setCurrentUserDisplayValue] = React.useState(\\"\\"); const [currentUserValue, setCurrentUserValue] = React.useState(undefined); const UserRef = React.createRef(); const [currentOrgDisplayValue, setCurrentOrgDisplayValue] = React.useState(\\"\\"); const [currentOrgValue, setCurrentOrgValue] = React.useState(undefined); const OrgRef = React.createRef(); const [ currentPostCommentsIdDisplayValue, setCurrentPostCommentsIdDisplayValue, ] = React.useState(\\"\\"); const [currentPostCommentsIdValue, setCurrentPostCommentsIdValue] = React.useState(undefined); const postCommentsIdRef = React.createRef(); const getIDValue = { post: (r) => JSON.stringify({ id: r?.id }), User: (r) => JSON.stringify({ id: r?.id }), Org: (r) => JSON.stringify({ id: r?.id }), }; const postIdSet = new Set( Array.isArray(post) ? post.map((r) => getIDValue.post?.(r)) : getIDValue.post?.(post) ); const UserIdSet = new Set( Array.isArray(User) ? User.map((r) => getIDValue.User?.(r)) : getIDValue.User?.(User) ); const OrgIdSet = new Set( Array.isArray(Org) ? Org.map((r) => getIDValue.Org?.(r)) : getIDValue.Org?.(Org) ); const postRecords = useDataStoreBinding({ type: \\"collection\\", model: Post, }).items; const userRecords = useDataStoreBinding({ type: \\"collection\\", model: User0, }).items; const orgRecords = useDataStoreBinding({ type: \\"collection\\", model: Org0, }).items; const getDisplayValue = { post: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, User: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Org: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, postCommentsId: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], post: [], User: [], Org: [{ type: \\"Required\\", validationMessage: \\"Org is required.\\" }], postCommentsId: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, post, User, Org, postCommentsId, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Comment(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCommentForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, post, User, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post: value, User, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.post ?? value; } setPost(value); setCurrentPostValue(undefined); setCurrentPostDisplayValue(\\"\\"); }} currentFieldValue={currentPostValue} label={\\"Post\\"} items={post ? [post] : []} hasError={errors?.post?.hasError} errorMessage={errors?.post?.errorMessage} getBadgeText={getDisplayValue.post} setFieldValue={(model) => { setCurrentPostDisplayValue(model ? getDisplayValue.post(model) : \\"\\"); setCurrentPostValue(model); }} inputFieldRef={postRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostDisplayValue} options={postRecords .filter((r) => !postIdSet.has(getIDValue.post?.(r))) .map((r) => ({ id: getIDValue.post?.(r), label: getDisplayValue.post?.(r), }))} onSelect={({ id, label }) => { setCurrentPostValue( postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostDisplayValue(label); runValidationTasks(\\"post\\", label); }} onClear={() => { setCurrentPostDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.post?.hasError) { runValidationTasks(\\"post\\", value); } setCurrentPostDisplayValue(value); setCurrentPostValue(undefined); }} onBlur={() => runValidationTasks(\\"post\\", currentPostDisplayValue)} errorMessage={errors.post?.errorMessage} hasError={errors.post?.hasError} ref={postRef} labelHidden={true} {...getOverrideProps(overrides, \\"post\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User: value, Org, postCommentsId, }; const result = onChange(modelFields); value = result?.User ?? value; } setUser(value); setCurrentUserValue(undefined); setCurrentUserDisplayValue(\\"\\"); }} currentFieldValue={currentUserValue} label={\\"User\\"} items={User ? [User] : []} hasError={errors?.User?.hasError} errorMessage={errors?.User?.errorMessage} getBadgeText={getDisplayValue.User} setFieldValue={(model) => { setCurrentUserDisplayValue(model ? getDisplayValue.User(model) : \\"\\"); setCurrentUserValue(model); }} inputFieldRef={UserRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"User\\" isRequired={false} isReadOnly={false} placeholder=\\"Search User\\" value={currentUserDisplayValue} options={userRecords .filter((r) => !UserIdSet.has(getIDValue.User?.(r))) .map((r) => ({ id: getIDValue.User?.(r), label: getDisplayValue.User?.(r), }))} onSelect={({ id, label }) => { setCurrentUserValue( userRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentUserDisplayValue(label); runValidationTasks(\\"User\\", label); }} onClear={() => { setCurrentUserDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.User?.hasError) { runValidationTasks(\\"User\\", value); } setCurrentUserDisplayValue(value); setCurrentUserValue(undefined); }} onBlur={() => runValidationTasks(\\"User\\", currentUserDisplayValue)} errorMessage={errors.User?.errorMessage} hasError={errors.User?.hasError} ref={UserRef} labelHidden={true} {...getOverrideProps(overrides, \\"User\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User, Org: value, postCommentsId, }; const result = onChange(modelFields); value = result?.Org ?? value; } setOrg(value); setCurrentOrgValue(undefined); setCurrentOrgDisplayValue(\\"\\"); }} currentFieldValue={currentOrgValue} label={\\"Org\\"} items={Org ? [Org] : []} hasError={errors?.Org?.hasError} errorMessage={errors?.Org?.errorMessage} getBadgeText={getDisplayValue.Org} setFieldValue={(model) => { setCurrentOrgDisplayValue(model ? getDisplayValue.Org(model) : \\"\\"); setCurrentOrgValue(model); }} inputFieldRef={OrgRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Org\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Org\\" value={currentOrgDisplayValue} options={orgRecords .filter((r) => !OrgIdSet.has(getIDValue.Org?.(r))) .map((r) => ({ id: getIDValue.Org?.(r), label: getDisplayValue.Org?.(r), }))} onSelect={({ id, label }) => { setCurrentOrgValue( orgRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentOrgDisplayValue(label); runValidationTasks(\\"Org\\", label); }} onClear={() => { setCurrentOrgDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Org?.hasError) { runValidationTasks(\\"Org\\", value); } setCurrentOrgDisplayValue(value); setCurrentOrgValue(undefined); }} onBlur={() => runValidationTasks(\\"Org\\", currentOrgDisplayValue)} errorMessage={errors.Org?.errorMessage} hasError={errors.Org?.hasError} ref={OrgRef} labelHidden={true} {...getOverrideProps(overrides, \\"Org\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, post, User, Org, postCommentsId: value, }; const result = onChange(modelFields); value = result?.postCommentsId ?? value; } setPostCommentsId(value); setCurrentPostCommentsIdValue(undefined); }} currentFieldValue={currentPostCommentsIdValue} label={\\"Post comments id\\"} items={postCommentsId ? [postCommentsId] : []} hasError={errors?.postCommentsId?.hasError} errorMessage={errors?.postCommentsId?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.postCommentsId( postRecords.find((r) => r.id === value) ) : \\"\\" } setFieldValue={(value) => { setCurrentPostCommentsIdDisplayValue( value ? getDisplayValue.postCommentsId( postRecords.find((r) => r.id === value) ) : \\"\\" ); setCurrentPostCommentsIdValue(value); }} inputFieldRef={postCommentsIdRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Post comments id\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostCommentsIdDisplayValue} options={postRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.postCommentsId?.(r), }))} onSelect={({ id, label }) => { setCurrentPostCommentsIdValue(id); setCurrentPostCommentsIdDisplayValue(label); runValidationTasks(\\"postCommentsId\\", label); }} onClear={() => { setCurrentPostCommentsIdDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.postCommentsId?.hasError) { runValidationTasks(\\"postCommentsId\\", value); } setCurrentPostCommentsIdDisplayValue(value); setCurrentPostCommentsIdValue(undefined); }} onBlur={() => runValidationTasks(\\"postCommentsId\\", currentPostCommentsIdValue) } errorMessage={errors.postCommentsId?.errorMessage} hasError={errors.postCommentsId?.hasError} ref={postCommentsIdRef} labelHidden={true} {...getOverrideProps(overrides, \\"postCommentsId\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for child of 1:m-belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post, User as User0, Org as Org0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCommentFormInputValues = { name?: string; post?: Post; User?: User0; Org?: Org0; postCommentsId?: string; }; export declare type CreateCommentFormValidationValues = { name?: ValidationFunction<string>; post?: ValidationFunction<Post>; User?: ValidationFunction<User0>; Org?: ValidationFunction<Org0>; postCommentsId?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCommentFormOverridesProps = { CreateCommentFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; post?: PrimitiveOverrideProps<AutocompleteProps>; User?: PrimitiveOverrideProps<AutocompleteProps>; Org?: PrimitiveOverrideProps<AutocompleteProps>; postCommentsId?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCommentFormProps = React.PropsWithChildren<{ overrides?: CreateCommentFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCommentFormInputValues) => CreateCommentFormInputValues; onSuccess?: (fields: CreateCommentFormInputValues) => void; onError?: (fields: CreateCommentFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCommentFormInputValues) => CreateCommentFormInputValues; onValidate?: CreateCommentFormValidationValues; } & React.CSSProperties>; export default function CreateCommentForm(props: CreateCommentFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for model with composite keys 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeDog, CompositeBowl as CompositeBowl0, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet, CompositeDogCompositeVet, } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateCompositeDogForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", description: \\"\\", CompositeBowl: undefined, CompositeOwner: undefined, CompositeToys: [], CompositeVets: [], }; const [name, setName] = React.useState(initialValues.name); const [description, setDescription] = React.useState( initialValues.description ); const [CompositeBowl, setCompositeBowl] = React.useState( initialValues.CompositeBowl ); const [CompositeOwner, setCompositeOwner] = React.useState( initialValues.CompositeOwner ); const [CompositeToys, setCompositeToys] = React.useState( initialValues.CompositeToys ); const [CompositeVets, setCompositeVets] = React.useState( initialValues.CompositeVets ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setDescription(initialValues.description); setCompositeBowl(initialValues.CompositeBowl); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); setCompositeOwner(initialValues.CompositeOwner); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); setCompositeToys(initialValues.CompositeToys); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); setCompositeVets(initialValues.CompositeVets); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); setErrors({}); }; const [ currentCompositeBowlDisplayValue, setCurrentCompositeBowlDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeBowlValue, setCurrentCompositeBowlValue] = React.useState(undefined); const CompositeBowlRef = React.createRef(); const [ currentCompositeOwnerDisplayValue, setCurrentCompositeOwnerDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeOwnerValue, setCurrentCompositeOwnerValue] = React.useState(undefined); const CompositeOwnerRef = React.createRef(); const [ currentCompositeToysDisplayValue, setCurrentCompositeToysDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeToysValue, setCurrentCompositeToysValue] = React.useState(undefined); const CompositeToysRef = React.createRef(); const [ currentCompositeVetsDisplayValue, setCurrentCompositeVetsDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeVetsValue, setCurrentCompositeVetsValue] = React.useState(undefined); const CompositeVetsRef = React.createRef(); const getIDValue = { CompositeBowl: (r) => JSON.stringify({ shape: r?.shape, size: r?.size }), CompositeOwner: (r) => JSON.stringify({ lastName: r?.lastName, firstName: r?.firstName }), CompositeToys: (r) => JSON.stringify({ kind: r?.kind, color: r?.color }), CompositeVets: (r) => JSON.stringify({ specialty: r?.specialty, city: r?.city }), }; const CompositeBowlIdSet = new Set( Array.isArray(CompositeBowl) ? CompositeBowl.map((r) => getIDValue.CompositeBowl?.(r)) : getIDValue.CompositeBowl?.(CompositeBowl) ); const CompositeOwnerIdSet = new Set( Array.isArray(CompositeOwner) ? CompositeOwner.map((r) => getIDValue.CompositeOwner?.(r)) : getIDValue.CompositeOwner?.(CompositeOwner) ); const CompositeToysIdSet = new Set( Array.isArray(CompositeToys) ? CompositeToys.map((r) => getIDValue.CompositeToys?.(r)) : getIDValue.CompositeToys?.(CompositeToys) ); const CompositeVetsIdSet = new Set( Array.isArray(CompositeVets) ? CompositeVets.map((r) => getIDValue.CompositeVets?.(r)) : getIDValue.CompositeVets?.(CompositeVets) ); const compositeBowlRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeBowl0, }).items; const compositeOwnerRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeOwner0, }).items; const compositeToyRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeToy, }).items; const compositeVetRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeVet, }).items; const getDisplayValue = { CompositeBowl: (r) => \`\${r?.shape}\${\\"-\\"}\${r?.size}\`, CompositeOwner: (r) => \`\${r?.lastName}\${\\"-\\"}\${r?.firstName}\`, CompositeToys: (r) => \`\${r?.kind}\${\\"-\\"}\${r?.color}\`, CompositeVets: (r) => \`\${r?.specialty}\${\\"-\\"}\${r?.city}\`, }; const validations = { name: [{ type: \\"Required\\" }], description: [{ type: \\"Required\\" }], CompositeBowl: [], CompositeOwner: [], CompositeToys: [], CompositeVets: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { name: modelFields.name, description: modelFields.description, CompositeBowl: modelFields.CompositeBowl, CompositeOwner: modelFields.CompositeOwner, }; const compositeDog = await DataStore.save( new CompositeDog(modelFieldsToSave) ); const promises = []; const compositeOwnerToLink = modelFields.CompositeOwner; if (compositeOwnerToLink) { promises.push( DataStore.save( CompositeOwner0.copyOf(compositeOwnerToLink, (updated) => { updated.CompositeDog = compositeDog; }) ) ); const compositeDogToUnlink = await compositeOwnerToLink.CompositeDog; if (compositeDogToUnlink) { promises.push( DataStore.save( CompositeDog.copyOf(compositeDogToUnlink, (updated) => { updated.CompositeOwner = undefined; updated.compositeDogCompositeOwnerLastName = undefined; updated.compositeDogCompositeOwnerFirstName = undefined; }) ) ); } } promises.push( ...CompositeToys.reduce((promises, original) => { promises.push( DataStore.save( CompositeToy.copyOf(original, (updated) => { updated.compositeDogCompositeToysName = compositeDog.name; updated.compositeDogCompositeToysDescription = compositeDog.description; }) ) ); return promises; }, []) ); promises.push( ...CompositeVets.reduce((promises, compositeVet) => { promises.push( DataStore.save( new CompositeDogCompositeVet({ compositeDog, compositeVet, }) ) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateCompositeDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"Description\\" isRequired={true} isReadOnly={false} value={description} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, description: value, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.description ?? value; } if (errors.description?.hasError) { runValidationTasks(\\"description\\", value); } setDescription(value); }} onBlur={() => runValidationTasks(\\"description\\", description)} errorMessage={errors.description?.errorMessage} hasError={errors.description?.hasError} {...getOverrideProps(overrides, \\"description\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl: value, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeBowl ?? value; } setCompositeBowl(value); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeBowlValue} label={\\"Composite bowl\\"} items={CompositeBowl ? [CompositeBowl] : []} hasError={errors?.CompositeBowl?.hasError} errorMessage={errors?.CompositeBowl?.errorMessage} getBadgeText={getDisplayValue.CompositeBowl} setFieldValue={(model) => { setCurrentCompositeBowlDisplayValue( model ? getDisplayValue.CompositeBowl(model) : \\"\\" ); setCurrentCompositeBowlValue(model); }} inputFieldRef={CompositeBowlRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite bowl\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeBowl\\" value={currentCompositeBowlDisplayValue} options={compositeBowlRecords .filter( (r) => !CompositeBowlIdSet.has(getIDValue.CompositeBowl?.(r)) ) .map((r) => ({ id: getIDValue.CompositeBowl?.(r), label: getDisplayValue.CompositeBowl?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeBowlValue( compositeBowlRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeBowlDisplayValue(label); runValidationTasks(\\"CompositeBowl\\", label); }} onClear={() => { setCurrentCompositeBowlDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeBowl?.hasError) { runValidationTasks(\\"CompositeBowl\\", value); } setCurrentCompositeBowlDisplayValue(value); setCurrentCompositeBowlValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeBowl\\", currentCompositeBowlDisplayValue ) } errorMessage={errors.CompositeBowl?.errorMessage} hasError={errors.CompositeBowl?.hasError} ref={CompositeBowlRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeBowl\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner: value, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeOwner ?? value; } setCompositeOwner(value); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeOwnerValue} label={\\"Composite owner\\"} items={CompositeOwner ? [CompositeOwner] : []} hasError={errors?.CompositeOwner?.hasError} errorMessage={errors?.CompositeOwner?.errorMessage} getBadgeText={getDisplayValue.CompositeOwner} setFieldValue={(model) => { setCurrentCompositeOwnerDisplayValue( model ? getDisplayValue.CompositeOwner(model) : \\"\\" ); setCurrentCompositeOwnerValue(model); }} inputFieldRef={CompositeOwnerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite owner\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeOwner\\" value={currentCompositeOwnerDisplayValue} options={compositeOwnerRecords .filter( (r) => !CompositeOwnerIdSet.has(getIDValue.CompositeOwner?.(r)) ) .map((r) => ({ id: getIDValue.CompositeOwner?.(r), label: getDisplayValue.CompositeOwner?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeOwnerValue( compositeOwnerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeOwnerDisplayValue(label); runValidationTasks(\\"CompositeOwner\\", label); }} onClear={() => { setCurrentCompositeOwnerDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeOwner?.hasError) { runValidationTasks(\\"CompositeOwner\\", value); } setCurrentCompositeOwnerDisplayValue(value); setCurrentCompositeOwnerValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeOwner\\", currentCompositeOwnerDisplayValue ) } errorMessage={errors.CompositeOwner?.errorMessage} hasError={errors.CompositeOwner?.hasError} ref={CompositeOwnerRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeOwner\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys: values, CompositeVets, }; const result = onChange(modelFields); values = result?.CompositeToys ?? values; } setCompositeToys(values); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeToysValue} label={\\"Composite toys\\"} items={CompositeToys} hasError={errors?.CompositeToys?.hasError} errorMessage={errors?.CompositeToys?.errorMessage} getBadgeText={getDisplayValue.CompositeToys} setFieldValue={(model) => { setCurrentCompositeToysDisplayValue( model ? getDisplayValue.CompositeToys(model) : \\"\\" ); setCurrentCompositeToysValue(model); }} inputFieldRef={CompositeToysRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite toys\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeToy\\" value={currentCompositeToysDisplayValue} options={compositeToyRecords .filter( (r) => !CompositeToysIdSet.has(getIDValue.CompositeToys?.(r)) ) .map((r) => ({ id: getIDValue.CompositeToys?.(r), label: getDisplayValue.CompositeToys?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeToysValue( compositeToyRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeToysDisplayValue(label); runValidationTasks(\\"CompositeToys\\", label); }} onClear={() => { setCurrentCompositeToysDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeToys?.hasError) { runValidationTasks(\\"CompositeToys\\", value); } setCurrentCompositeToysDisplayValue(value); setCurrentCompositeToysValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeToys\\", currentCompositeToysDisplayValue ) } errorMessage={errors.CompositeToys?.errorMessage} hasError={errors.CompositeToys?.hasError} ref={CompositeToysRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeToys\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets: values, }; const result = onChange(modelFields); values = result?.CompositeVets ?? values; } setCompositeVets(values); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeVetsValue} label={\\"Composite vets\\"} items={CompositeVets} hasError={errors?.CompositeVets?.hasError} errorMessage={errors?.CompositeVets?.errorMessage} getBadgeText={getDisplayValue.CompositeVets} setFieldValue={(model) => { setCurrentCompositeVetsDisplayValue( model ? getDisplayValue.CompositeVets(model) : \\"\\" ); setCurrentCompositeVetsValue(model); }} inputFieldRef={CompositeVetsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite vets\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeVet\\" value={currentCompositeVetsDisplayValue} options={compositeVetRecords .filter( (r) => !CompositeVetsIdSet.has(getIDValue.CompositeVets?.(r)) ) .map((r) => ({ id: getIDValue.CompositeVets?.(r), label: getDisplayValue.CompositeVets?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeVetsValue( compositeVetRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeVetsDisplayValue(label); runValidationTasks(\\"CompositeVets\\", label); }} onClear={() => { setCurrentCompositeVetsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeVets?.hasError) { runValidationTasks(\\"CompositeVets\\", value); } setCurrentCompositeVetsDisplayValue(value); setCurrentCompositeVetsValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeVets\\", currentCompositeVetsDisplayValue ) } errorMessage={errors.CompositeVets?.errorMessage} hasError={errors.CompositeVets?.hasError} ref={CompositeVetsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeVets\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for model with composite keys 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeBowl as CompositeBowl0, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateCompositeDogFormInputValues = { name?: string; description?: string; CompositeBowl?: CompositeBowl0; CompositeOwner?: CompositeOwner0; CompositeToys?: CompositeToy[]; CompositeVets?: CompositeVet[]; }; export declare type CreateCompositeDogFormValidationValues = { name?: ValidationFunction<string>; description?: ValidationFunction<string>; CompositeBowl?: ValidationFunction<CompositeBowl0>; CompositeOwner?: ValidationFunction<CompositeOwner0>; CompositeToys?: ValidationFunction<CompositeToy>; CompositeVets?: ValidationFunction<CompositeVet>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateCompositeDogFormOverridesProps = { CreateCompositeDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; description?: PrimitiveOverrideProps<TextFieldProps>; CompositeBowl?: PrimitiveOverrideProps<AutocompleteProps>; CompositeOwner?: PrimitiveOverrideProps<AutocompleteProps>; CompositeToys?: PrimitiveOverrideProps<AutocompleteProps>; CompositeVets?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateCompositeDogFormProps = React.PropsWithChildren<{ overrides?: CreateCompositeDogFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateCompositeDogFormInputValues) => CreateCompositeDogFormInputValues; onSuccess?: (fields: CreateCompositeDogFormInputValues) => void; onError?: (fields: CreateCompositeDogFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateCompositeDogFormInputValues) => CreateCompositeDogFormInputValues; onValidate?: CreateCompositeDogFormValidationValues; } & React.CSSProperties>; export default function CreateCompositeDogForm(props: CreateCompositeDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, Radio, RadioGroupField, SelectField, StepperField, TextField, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; export default function CustomDataForm(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"John Doe\\", email: \\"johndoe@amplify.com\\", city: \\"\\", category: undefined, pages: 0, phone: \\"+1-401-152-6995\\", }; const [name, setName] = React.useState(initialValues.name); const [email, setEmail] = React.useState(initialValues.email); const [city, setCity] = React.useState(initialValues.city); const [category, setCategory] = React.useState(initialValues.category); const [pages, setPages] = React.useState(initialValues.pages); const [phone, setPhone] = React.useState(initialValues.phone); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setEmail(initialValues.email); setCity(initialValues.city); setCategory(initialValues.category); setPages(initialValues.pages); setPhone(initialValues.phone); setErrors({}); }; const validations = { name: [{ type: \\"Required\\" }], email: [{ type: \\"Required\\" }], city: [], category: [], pages: [], phone: [{ type: \\"Required\\" }, { type: \\"Phone\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, email, city, category, pages, phone, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomDataForm\\")} {...rest} > <TextField label=\\"name\\" isRequired={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, email, city, category, pages, phone, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"E-mail\\" isRequired={true} value={email} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email: value, city, category, pages, phone, }; const result = onChange(modelFields); value = result?.email ?? value; } if (errors.email?.hasError) { runValidationTasks(\\"email\\", value); } setEmail(value); }} onBlur={() => runValidationTasks(\\"email\\", email)} errorMessage={errors.email?.errorMessage} hasError={errors.email?.hasError} {...getOverrideProps(overrides, \\"email\\")} ></TextField> <SelectField label=\\"Label\\" placeholder=\\"Please select an option\\" value={city} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city: value, category, pages, phone, }; const result = onChange(modelFields); value = result?.city ?? value; } if (errors.city?.hasError) { runValidationTasks(\\"city\\", value); } setCity(value); }} onBlur={() => runValidationTasks(\\"city\\", city)} errorMessage={errors.city?.errorMessage} hasError={errors.city?.hasError} {...getOverrideProps(overrides, \\"city\\")} > <option children=\\"Los Angeles\\" value=\\"Los Angeles\\" {...getOverrideProps(overrides, \\"cityoption0\\")} ></option> <option children=\\"Houston\\" value=\\"Houston\\" {...getOverrideProps(overrides, \\"cityoption1\\")} ></option> <option children=\\"New York\\" value=\\"New York\\" selected={true} {...getOverrideProps(overrides, \\"cityoption2\\")} ></option> </SelectField> <RadioGroupField label=\\"Label\\" name=\\"fieldName\\" defaultValue=\\"Hobbies\\" onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city, category: value, pages, phone, }; const result = onChange(modelFields); value = result?.category ?? value; } if (errors.category?.hasError) { runValidationTasks(\\"category\\", value); } setCategory(value); }} onBlur={() => runValidationTasks(\\"category\\", category)} errorMessage={errors.category?.errorMessage} hasError={errors.category?.hasError} {...getOverrideProps(overrides, \\"category\\")} > <Radio children=\\"Hobbies\\" value=\\"Hobbies\\" {...getOverrideProps(overrides, \\"categoryRadio0\\")} ></Radio> <Radio children=\\"Travel\\" value=\\"Travel\\" {...getOverrideProps(overrides, \\"categoryRadio1\\")} ></Radio> <Radio children=\\"Health\\" value=\\"Health\\" {...getOverrideProps(overrides, \\"categoryRadio2\\")} ></Radio> </RadioGroupField> <StepperField label=\\"Label\\" value={pages} onStepChange={(e) => { let value = e; if (onChange) { const modelFields = { name, email, city, category, pages: value, phone, }; const result = onChange(modelFields); value = result?.pages ?? value; } if (errors.pages?.hasError) { runValidationTasks(\\"pages\\", value); } setPages(value); }} onBlur={() => runValidationTasks(\\"pages\\", pages)} errorMessage={errors.pages?.errorMessage} hasError={errors.pages?.hasError} {...getOverrideProps(overrides, \\"pages\\")} ></StepperField> <TextField label=\\"Phone Number\\" isRequired={true} type=\\"tel\\" value={phone} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city, category, pages, phone: value, }; const result = onChange(modelFields); value = result?.phone ?? value; } if (errors.phone?.hasError) { runValidationTasks(\\"phone\\", value); } setPhone(value); }} onBlur={() => runValidationTasks(\\"phone\\", phone)} errorMessage={errors.phone?.errorMessage} hasError={errors.phone?.hasError} {...getOverrideProps(overrides, \\"phone\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"empty\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"go back\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed create form 2`] = ` "import * as React from \\"react\\"; import { GridProps, RadioGroupFieldProps, SelectFieldProps, StepperFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomDataFormInputValues = { name?: string; email?: string; city?: string; category?: string; pages?: number; phone?: string; }; export declare type CustomDataFormValidationValues = { name?: ValidationFunction<string>; email?: ValidationFunction<string>; city?: ValidationFunction<string>; category?: ValidationFunction<string>; pages?: ValidationFunction<number>; phone?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomDataFormOverridesProps = { CustomDataFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; email?: PrimitiveOverrideProps<TextFieldProps>; city?: PrimitiveOverrideProps<SelectFieldProps>; category?: PrimitiveOverrideProps<RadioGroupFieldProps>; pages?: PrimitiveOverrideProps<StepperFieldProps>; phone?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type CustomDataFormProps = React.PropsWithChildren<{ overrides?: CustomDataFormOverridesProps | undefined | null; } & { onSubmit: (fields: CustomDataFormInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues; onValidate?: CustomDataFormValidationValues; } & React.CSSProperties>; export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed create form with styled gaps 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, Radio, RadioGroupField, SelectField, StepperField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; export default function CustomDataForm(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const { tokens } = useTheme(); const initialValues = { name: \\"John Doe\\", email: \\"johndoe@amplify.com\\", city: \\"\\", category: undefined, pages: 0, phone: \\"+1-401-152-6995\\", }; const [name, setName] = React.useState(initialValues.name); const [email, setEmail] = React.useState(initialValues.email); const [city, setCity] = React.useState(initialValues.city); const [category, setCategory] = React.useState(initialValues.category); const [pages, setPages] = React.useState(initialValues.pages); const [phone, setPhone] = React.useState(initialValues.phone); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setEmail(initialValues.email); setCity(initialValues.city); setCategory(initialValues.category); setPages(initialValues.pages); setPhone(initialValues.phone); setErrors({}); }; const validations = { name: [{ type: \\"Required\\" }], email: [{ type: \\"Required\\" }], city: [], category: [], pages: [], phone: [{ type: \\"Required\\" }, { type: \\"Phone\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap={tokens.space.large.value} columnGap={tokens.space.xl.value} padding=\\"35px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, email, city, category, pages, phone, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomDataForm\\")} {...rest} > <TextField label=\\"name\\" isRequired={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, email, city, category, pages, phone, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"E-mail\\" isRequired={true} value={email} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email: value, city, category, pages, phone, }; const result = onChange(modelFields); value = result?.email ?? value; } if (errors.email?.hasError) { runValidationTasks(\\"email\\", value); } setEmail(value); }} onBlur={() => runValidationTasks(\\"email\\", email)} errorMessage={errors.email?.errorMessage} hasError={errors.email?.hasError} {...getOverrideProps(overrides, \\"email\\")} ></TextField> <SelectField label=\\"Label\\" placeholder=\\"Please select an option\\" value={city} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city: value, category, pages, phone, }; const result = onChange(modelFields); value = result?.city ?? value; } if (errors.city?.hasError) { runValidationTasks(\\"city\\", value); } setCity(value); }} onBlur={() => runValidationTasks(\\"city\\", city)} errorMessage={errors.city?.errorMessage} hasError={errors.city?.hasError} {...getOverrideProps(overrides, \\"city\\")} > <option children=\\"Los Angeles\\" value=\\"Los Angeles\\" {...getOverrideProps(overrides, \\"cityoption0\\")} ></option> <option children=\\"Houston\\" value=\\"Houston\\" {...getOverrideProps(overrides, \\"cityoption1\\")} ></option> <option children=\\"New York\\" value=\\"New York\\" selected={true} {...getOverrideProps(overrides, \\"cityoption2\\")} ></option> </SelectField> <RadioGroupField label=\\"Label\\" name=\\"fieldName\\" defaultValue=\\"Hobbies\\" onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city, category: value, pages, phone, }; const result = onChange(modelFields); value = result?.category ?? value; } if (errors.category?.hasError) { runValidationTasks(\\"category\\", value); } setCategory(value); }} onBlur={() => runValidationTasks(\\"category\\", category)} errorMessage={errors.category?.errorMessage} hasError={errors.category?.hasError} {...getOverrideProps(overrides, \\"category\\")} > <Radio children=\\"Hobbies\\" value=\\"Hobbies\\" {...getOverrideProps(overrides, \\"categoryRadio0\\")} ></Radio> <Radio children=\\"Travel\\" value=\\"Travel\\" {...getOverrideProps(overrides, \\"categoryRadio1\\")} ></Radio> <Radio children=\\"Health\\" value=\\"Health\\" {...getOverrideProps(overrides, \\"categoryRadio2\\")} ></Radio> </RadioGroupField> <StepperField label=\\"Label\\" value={pages} onStepChange={(e) => { let value = e; if (onChange) { const modelFields = { name, email, city, category, pages: value, phone, }; const result = onChange(modelFields); value = result?.pages ?? value; } if (errors.pages?.hasError) { runValidationTasks(\\"pages\\", value); } setPages(value); }} onBlur={() => runValidationTasks(\\"pages\\", pages)} errorMessage={errors.pages?.errorMessage} hasError={errors.pages?.hasError} {...getOverrideProps(overrides, \\"pages\\")} ></StepperField> <TextField label=\\"Phone Number\\" isRequired={true} type=\\"tel\\" value={phone} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, city, category, pages, phone: value, }; const result = onChange(modelFields); value = result?.phone ?? value; } if (errors.phone?.hasError) { runValidationTasks(\\"phone\\", value); } setPhone(value); }} onBlur={() => runValidationTasks(\\"phone\\", phone)} errorMessage={errors.phone?.errorMessage} hasError={errors.phone?.hasError} {...getOverrideProps(overrides, \\"phone\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"empty\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap={tokens.space.xl.value} {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"go back\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed create form with styled gaps 2`] = ` "import * as React from \\"react\\"; import { GridProps, RadioGroupFieldProps, SelectFieldProps, StepperFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomDataFormInputValues = { name?: string; email?: string; city?: string; category?: string; pages?: number; phone?: string; }; export declare type CustomDataFormValidationValues = { name?: ValidationFunction<string>; email?: ValidationFunction<string>; city?: ValidationFunction<string>; category?: ValidationFunction<string>; pages?: ValidationFunction<number>; phone?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomDataFormOverridesProps = { CustomDataFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; email?: PrimitiveOverrideProps<TextFieldProps>; city?: PrimitiveOverrideProps<SelectFieldProps>; category?: PrimitiveOverrideProps<RadioGroupFieldProps>; pages?: PrimitiveOverrideProps<StepperFieldProps>; phone?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type CustomDataFormProps = React.PropsWithChildren<{ overrides?: CustomDataFormOverridesProps | undefined | null; } & { onSubmit: (fields: CustomDataFormInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues; onValidate?: CustomDataFormValidationValues; } & React.CSSProperties>; export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed form with an array field 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CustomDataForm(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"John Doe\\", email: [\\"johndoe@amplify.com\\"], phone: [\\"+1-401-152-6995\\"], city: undefined, }; const [name, setName] = React.useState(initialValues.name); const [email, setEmail] = React.useState(initialValues.email); const [phone, setPhone] = React.useState(initialValues.phone); const [city, setCity] = React.useState(initialValues.city); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setEmail(initialValues.email); setCurrentEmailValue(\\"\\"); setPhone(initialValues.phone); setCurrentPhoneValue(\\"\\"); setCity(initialValues.city); setErrors({}); }; const [currentEmailValue, setCurrentEmailValue] = React.useState(\\"\\"); const emailRef = React.createRef(); const [currentPhoneValue, setCurrentPhoneValue] = React.useState(\\"\\"); const phoneRef = React.createRef(); const validations = { name: [{ type: \\"Required\\" }], email: [{ type: \\"Required\\" }], phone: [{ type: \\"Required\\" }, { type: \\"Phone\\" }], city: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, email, phone, city, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomDataForm\\")} {...rest} > <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>name</span> <span style={{ color: \\"red\\" }}>*</span> </span> } isRequired={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, email, phone, city, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, email: values, phone, city, }; const result = onChange(modelFields); values = result?.email ?? values; } setEmail(values); setCurrentEmailValue(\\"\\"); }} currentFieldValue={currentEmailValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>E-mail</span> <span style={{ color: \\"red\\" }}>*</span> </span> } items={email} hasError={errors?.email?.hasError} errorMessage={errors?.email?.errorMessage} setFieldValue={setCurrentEmailValue} inputFieldRef={emailRef} defaultFieldValue={\\"\\"} > <TextField label=\\"E-mail\\" isRequired={true} value={currentEmailValue} onChange={(e) => { let { value } = e.target; if (errors.email?.hasError) { runValidationTasks(\\"email\\", value); } setCurrentEmailValue(value); }} onBlur={() => runValidationTasks(\\"email\\", currentEmailValue)} errorMessage={errors.email?.errorMessage} hasError={errors.email?.hasError} ref={emailRef} labelHidden={true} {...getOverrideProps(overrides, \\"email\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, email, phone: values, city, }; const result = onChange(modelFields); values = result?.phone ?? values; } setPhone(values); setCurrentPhoneValue(\\"\\"); }} currentFieldValue={currentPhoneValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>phone</span> <span style={{ color: \\"red\\" }}>*</span> </span> } items={phone} hasError={errors?.phone?.hasError} errorMessage={errors?.phone?.errorMessage} setFieldValue={setCurrentPhoneValue} inputFieldRef={phoneRef} defaultFieldValue={\\"\\"} > <TextField label=\\"phone\\" isRequired={true} type=\\"tel\\" value={currentPhoneValue} onChange={(e) => { let { value } = e.target; if (errors.phone?.hasError) { runValidationTasks(\\"phone\\", value); } setCurrentPhoneValue(value); }} onBlur={() => runValidationTasks(\\"phone\\", currentPhoneValue)} errorMessage={errors.phone?.errorMessage} hasError={errors.phone?.hasError} ref={phoneRef} labelHidden={true} {...getOverrideProps(overrides, \\"phone\\")} ></TextField> </ArrayField> <Autocomplete label=\\"City\\" options={[ { id: \\"SF\\", label: \\"SF\\", }, { id: \\"LA\\", label: \\"LA\\", }, ]} onSelect={({ id, label }) => { setCity(id); runValidationTasks(\\"city\\", id); }} onClear={() => { setCity(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, phone, city: value, }; const result = onChange(modelFields); value = result?.city ?? value; } if (errors.city?.hasError) { runValidationTasks(\\"city\\", value); } setCity(value); }} onBlur={() => runValidationTasks(\\"city\\", city)} errorMessage={errors.city?.errorMessage} hasError={errors.city?.hasError} labelHidden={false} {...getOverrideProps(overrides, \\"city\\")} ></Autocomplete> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"empty\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"go back\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed form with an array field 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomDataFormInputValues = { name?: string; email?: string[]; phone?: string[]; city?: string; }; export declare type CustomDataFormValidationValues = { name?: ValidationFunction<string>; email?: ValidationFunction<string>; phone?: ValidationFunction<string>; city?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomDataFormOverridesProps = { CustomDataFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; email?: PrimitiveOverrideProps<TextFieldProps>; phone?: PrimitiveOverrideProps<TextFieldProps>; city?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CustomDataFormProps = React.PropsWithChildren<{ overrides?: CustomDataFormOverridesProps | undefined | null; } & { onSubmit: (fields: CustomDataFormInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues; onValidate?: CustomDataFormValidationValues; } & React.CSSProperties>; export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, Radio, RadioGroupField, SelectField, StepperField, TextAreaField, TextField, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; export default function CustomDataForm(props) { const { initialData, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"John Doe\\", email: \\"johndoe@amplify.com\\", \\"metadata-field\\": \\"\\", city: \\"\\", category: undefined, pages: 0, }; const [name, setName] = React.useState(initialValues.name); const [email, setEmail] = React.useState(initialValues.email); const [metadataField, setMetadataField] = React.useState( initialValues[\\"metadata-field\\"] ); const [city, setCity] = React.useState(initialValues.city); const [category, setCategory] = React.useState(initialValues.category); const [pages, setPages] = React.useState(initialValues.pages); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = initialData ? { ...initialValues, ...initialData } : initialValues; setName(cleanValues.name); setEmail(cleanValues.email); setMetadataField(cleanValues[\\"metadata-field\\"]); setCity(cleanValues.city); setCategory(cleanValues.category); setPages(cleanValues.pages); setErrors({}); }; React.useEffect(resetStateValues, [initialData]); React.useEffect(() => { if (initialData) { setName(initialData.name); setEmail(initialData.email); setMetadataField(initialData[\\"metadata-field\\"]); setCity(initialData.city); setCategory(initialData.category); setPages(initialData.pages); } }, []); const validations = { name: [{ type: \\"Required\\" }], email: [{ type: \\"Required\\" }], \\"metadata-field\\": [{ type: \\"Required\\" }, { type: \\"JSON\\" }], city: [], category: [], pages: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, email, \\"metadata-field\\": metadataField, city, category, pages, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomDataForm\\")} {...rest} > <TextField label=\\"name\\" isRequired={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, email, \\"metadata-field\\": metadataField, city, category, pages, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"E-mail\\" isRequired={true} value={email} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email: value, \\"metadata-field\\": metadataField, city, category, pages, }; const result = onChange(modelFields); value = result?.email ?? value; } if (errors.email?.hasError) { runValidationTasks(\\"email\\", value); } setEmail(value); }} onBlur={() => runValidationTasks(\\"email\\", email)} errorMessage={errors.email?.errorMessage} hasError={errors.email?.hasError} {...getOverrideProps(overrides, \\"email\\")} ></TextField> <TextAreaField label=\\"Metadata\\" isRequired={true} value={metadataField} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, \\"metadata-field\\": value, city, category, pages, }; const result = onChange(modelFields); value = result?.[\\"metadata-field\\"] ?? value; } if (errors[\\"metadata-field\\"]?.hasError) { runValidationTasks(\\"metadata-field\\", value); } setMetadataField(value); }} onBlur={() => runValidationTasks(\\"metadata-field\\", metadataField)} errorMessage={errors[\\"metadata-field\\"]?.errorMessage} hasError={errors[\\"metadata-field\\"]?.hasError} {...getOverrideProps(overrides, \\"metadata-field\\")} ></TextAreaField> <SelectField label=\\"Label\\" placeholder=\\"Please select an option\\" value={city} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, \\"metadata-field\\": metadataField, city: value, category, pages, }; const result = onChange(modelFields); value = result?.city ?? value; } if (errors.city?.hasError) { runValidationTasks(\\"city\\", value); } setCity(value); }} onBlur={() => runValidationTasks(\\"city\\", city)} errorMessage={errors.city?.errorMessage} hasError={errors.city?.hasError} {...getOverrideProps(overrides, \\"city\\")} > <option children=\\"Los Angeles\\" value=\\"Los Angeles\\" {...getOverrideProps(overrides, \\"cityoption0\\")} ></option> <option children=\\"Houston\\" value=\\"Houston\\" {...getOverrideProps(overrides, \\"cityoption1\\")} ></option> <option children=\\"New York\\" value=\\"New York\\" selected={true} {...getOverrideProps(overrides, \\"cityoption2\\")} ></option> </SelectField> <RadioGroupField label=\\"Label\\" name=\\"fieldName\\" defaultValue=\\"Hobbies\\" defaultValue={category} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, email, \\"metadata-field\\": metadataField, city, category: value, pages, }; const result = onChange(modelFields); value = result?.category ?? value; } if (errors.category?.hasError) { runValidationTasks(\\"category\\", value); } setCategory(value); }} onBlur={() => runValidationTasks(\\"category\\", category)} errorMessage={errors.category?.errorMessage} hasError={errors.category?.hasError} {...getOverrideProps(overrides, \\"category\\")} > <Radio children=\\"Hobbies\\" value=\\"Hobbies\\" {...getOverrideProps(overrides, \\"categoryRadio0\\")} ></Radio> <Radio children=\\"Travel\\" value=\\"Travel\\" {...getOverrideProps(overrides, \\"categoryRadio1\\")} ></Radio> <Radio children=\\"Health\\" value=\\"Health\\" {...getOverrideProps(overrides, \\"categoryRadio2\\")} ></Radio> </RadioGroupField> <StepperField label=\\"Label\\" value={pages} onStepChange={(e) => { let value = e; if (onChange) { const modelFields = { name, email, \\"metadata-field\\": metadataField, city, category, pages: value, }; const result = onChange(modelFields); value = result?.pages ?? value; } if (errors.pages?.hasError) { runValidationTasks(\\"pages\\", value); } setPages(value); }} onBlur={() => runValidationTasks(\\"pages\\", pages)} errorMessage={errors.pages?.errorMessage} hasError={errors.pages?.hasError} {...getOverrideProps(overrides, \\"pages\\")} ></StepperField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"empty\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"go back\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a custom backed update form 2`] = ` "import * as React from \\"react\\"; import { GridProps, RadioGroupFieldProps, SelectFieldProps, StepperFieldProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomDataFormInputValues = { name?: string; email?: string; \\"metadata-field\\"?: string; city?: string; category?: string; pages?: number; }; export declare type CustomDataFormValidationValues = { name?: ValidationFunction<string>; email?: ValidationFunction<string>; \\"metadata-field\\"?: ValidationFunction<string>; city?: ValidationFunction<string>; category?: ValidationFunction<string>; pages?: ValidationFunction<number>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomDataFormOverridesProps = { CustomDataFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; email?: PrimitiveOverrideProps<TextFieldProps>; \\"metadata-field\\"?: PrimitiveOverrideProps<TextAreaFieldProps>; city?: PrimitiveOverrideProps<SelectFieldProps>; category?: PrimitiveOverrideProps<RadioGroupFieldProps>; pages?: PrimitiveOverrideProps<StepperFieldProps>; } & EscapeHatchProps; export declare type CustomDataFormProps = React.PropsWithChildren<{ overrides?: CustomDataFormOverridesProps | undefined | null; } & { initialData?: CustomDataFormInputValues; onSubmit: (fields: CustomDataFormInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues; onValidate?: CustomDataFormValidationValues; } & React.CSSProperties>; export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a datastore backed form with a custom array field 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyPostForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { username: \\"\\", caption: \\"\\", Customtags: [], post_url: \\"\\", metadata: \\"\\", profile_url: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [username, setUsername] = React.useState(initialValues.username); const [caption, setCaption] = React.useState(initialValues.caption); const [Customtags, setCustomtags] = React.useState(initialValues.Customtags); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setUsername(initialValues.username); setCaption(initialValues.caption); setCustomtags(initialValues.Customtags); setCurrentCustomtagsValue(\\"\\"); setPost_url(initialValues.post_url); setMetadata(initialValues.metadata); setProfile_url(initialValues.profile_url); setNonModelField(initialValues.nonModelField); setNonModelFieldArray(initialValues.nonModelFieldArray); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [currentCustomtagsValue, setCurrentCustomtagsValue] = React.useState(\\"\\"); const CustomtagsRef = React.createRef(); const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { username: [ { type: \\"GreaterThanChar\\", numValues: [2], validationMessage: \\"needs to be of length 2\\", }, ], caption: [], Customtags: [], post_url: [{ type: \\"URL\\" }], metadata: [{ type: \\"JSON\\" }], profile_url: [{ type: \\"URL\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { username, caption, Customtags, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { username: modelFields.username, caption: modelFields.caption, post_url: modelFields.post_url, metadata: modelFields.metadata, profile_url: modelFields.profile_url, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await DataStore.save(new Post(modelFieldsToSave)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <Grid columnGap=\\"inherit\\" rowGap=\\"inherit\\" templateColumns=\\"repeat(2, auto)\\" {...getOverrideProps(overrides, \\"RowGrid0\\")} > <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} placeholder=\\"john\\" value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username: value, caption, Customtags, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} placeholder=\\"i love code\\" value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption: value, Customtags, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> </Grid> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags: values, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); values = result?.Customtags ?? values; } setCustomtags(values); setCurrentCustomtagsValue(\\"\\"); }} currentFieldValue={currentCustomtagsValue} label={\\"Tags\\"} items={Customtags} hasError={errors?.Customtags?.hasError} errorMessage={errors?.Customtags?.errorMessage} setFieldValue={setCurrentCustomtagsValue} inputFieldRef={CustomtagsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Tags\\" placeholder=\\"goals\\" value={currentCustomtagsValue} onChange={(e) => { let { value } = e.target; if (errors.Customtags?.hasError) { runValidationTasks(\\"Customtags\\", value); } setCurrentCustomtagsValue(value); }} onBlur={() => runValidationTasks(\\"Customtags\\", currentCustomtagsValue) } errorMessage={errors.Customtags?.errorMessage} hasError={errors.Customtags?.hasError} ref={CustomtagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Customtags\\")} ></TextField> </ArrayField> <TextField label=\\"Post url\\" isRequired={false} isReadOnly={false} value={post_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, post_url: value, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} ></TextField> <TextAreaField label=\\"Metadata\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, post_url, metadata: value, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} ></TextAreaField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, post_url, metadata, profile_url: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> <TextAreaField label=\\"Non model field\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, post_url, metadata, profile_url, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags, post_url, metadata, profile_url, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Non model field array\\" isRequired={false} isReadOnly={false} value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} ></TextAreaField> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a datastore backed form with a custom array field 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyPostFormInputValues = { username?: string; caption?: string; Customtags?: string[]; post_url?: string; metadata?: string; profile_url?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type MyPostFormValidationValues = { username?: ValidationFunction<string>; caption?: ValidationFunction<string>; Customtags?: ValidationFunction<string>; post_url?: ValidationFunction<string>; metadata?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; nonModelField?: ValidationFunction<string>; nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyPostFormOverridesProps = { MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; RowGrid0?: PrimitiveOverrideProps<GridProps>; username?: PrimitiveOverrideProps<TextFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; Customtags?: PrimitiveOverrideProps<TextFieldProps>; post_url?: PrimitiveOverrideProps<TextFieldProps>; metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; export declare type MyPostFormProps = React.PropsWithChildren<{ overrides?: MyPostFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onSuccess?: (fields: MyPostFormInputValues) => void; onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a update form for parent of 1:m-belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Org, Comment } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateOrgForm(props) { const { id: idProp, org: orgModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", comments: [], }; const [name, setName] = React.useState(initialValues.name); const [comments, setComments] = React.useState(initialValues.comments); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = orgRecord ? { ...initialValues, ...orgRecord, comments: linkedComments } : initialValues; setName(cleanValues.name); setComments(cleanValues.comments ?? []); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; const [orgRecord, setOrgRecord] = React.useState(orgModelProp); const [linkedComments, setLinkedComments] = React.useState([]); const canUnlinkComments = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Org, idProp) : orgModelProp; setOrgRecord(record); const linkedComments = record ? await record.comments.toArray() : []; setLinkedComments(linkedComments); }; queryData(); }, [idProp, orgModelProp]); React.useEffect(resetStateValues, [orgRecord, linkedComments]); const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = React.useState(\\"\\"); const [currentCommentsValue, setCurrentCommentsValue] = React.useState(undefined); const commentsRef = React.createRef(); const getIDValue = { comments: (r) => JSON.stringify({ id: r?.id }), }; const commentsIdSet = new Set( Array.isArray(comments) ? comments.map((r) => getIDValue.comments?.(r)) : getIDValue.comments?.(comments) ); const commentRecords = useDataStoreBinding({ type: \\"collection\\", model: Comment, }).items; const getDisplayValue = { comments: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], comments: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, comments, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const commentsToLink = []; const commentsToUnLink = []; const commentsSet = new Set(); const linkedCommentsSet = new Set(); comments.forEach((r) => commentsSet.add(getIDValue.comments?.(r))); linkedComments.forEach((r) => linkedCommentsSet.add(getIDValue.comments?.(r)) ); linkedComments.forEach((r) => { if (!commentsSet.has(getIDValue.comments?.(r))) { commentsToUnLink.push(r); } }); comments.forEach((r) => { if (!linkedCommentsSet.has(getIDValue.comments?.(r))) { commentsToLink.push(r); } }); commentsToUnLink.forEach((original) => { if (!canUnlinkComments) { throw Error( \`Comment \${original.id} cannot be unlinked from Org because orgCommentsId is a required field.\` ); } promises.push( DataStore.save( Comment.copyOf(original, (updated) => { updated.orgCommentsId = null; updated.Org = null; }) ) ); }); commentsToLink.forEach((original) => { promises.push( DataStore.save( Comment.copyOf(original, (updated) => { updated.orgCommentsId = orgRecord.id; updated.Org = orgRecord; }) ) ); }); const modelFieldsToSave = { name: modelFields.name, }; promises.push( DataStore.save( Org.copyOf(orgRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateOrgForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, comments, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, comments: values, }; const result = onChange(modelFields); values = result?.comments ?? values; } setComments(values); setCurrentCommentsValue(undefined); setCurrentCommentsDisplayValue(\\"\\"); }} currentFieldValue={currentCommentsValue} label={\\"Comments\\"} items={comments} hasError={errors?.comments?.hasError} errorMessage={errors?.comments?.errorMessage} getBadgeText={getDisplayValue.comments} setFieldValue={(model) => { setCurrentCommentsDisplayValue( model ? getDisplayValue.comments(model) : \\"\\" ); setCurrentCommentsValue(model); }} inputFieldRef={commentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Comments\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Comment\\" value={currentCommentsDisplayValue} options={commentRecords .filter((r) => !commentsIdSet.has(getIDValue.comments?.(r))) .map((r) => ({ id: getIDValue.comments?.(r), label: getDisplayValue.comments?.(r), }))} onSelect={({ id, label }) => { setCurrentCommentsValue( commentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCommentsDisplayValue(label); runValidationTasks(\\"comments\\", label); }} onClear={() => { setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.comments?.hasError) { runValidationTasks(\\"comments\\", value); } setCurrentCommentsDisplayValue(value); setCurrentCommentsValue(undefined); }} onBlur={() => runValidationTasks(\\"comments\\", currentCommentsDisplayValue) } errorMessage={errors.comments?.errorMessage} hasError={errors.comments?.hasError} ref={commentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"comments\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || orgModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || orgModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render a update form for parent of 1:m-belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Org, Comment } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateOrgFormInputValues = { name?: string; comments?: Comment[]; }; export declare type UpdateOrgFormValidationValues = { name?: ValidationFunction<string>; comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateOrgFormOverridesProps = { UpdateOrgFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateOrgFormProps = React.PropsWithChildren<{ overrides?: UpdateOrgFormOverridesProps | undefined | null; } & { id?: string; org?: Org; onSubmit?: (fields: UpdateOrgFormInputValues) => UpdateOrgFormInputValues; onSuccess?: (fields: UpdateOrgFormInputValues) => void; onError?: (fields: UpdateOrgFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateOrgFormInputValues) => UpdateOrgFormInputValues; onValidate?: UpdateOrgFormValidationValues; } & React.CSSProperties>; export default function UpdateOrgForm(props: UpdateOrgFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render an update form for model with composite keys 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeDog, CompositeBowl as CompositeBowl0, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet, CompositeDogCompositeVet, } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateCompositeDogForm(props) { const { id: idProp, compositeDog: compositeDogModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", description: \\"\\", CompositeBowl: undefined, CompositeOwner: undefined, CompositeToys: [], CompositeVets: [], }; const [name, setName] = React.useState(initialValues.name); const [description, setDescription] = React.useState( initialValues.description ); const [CompositeBowl, setCompositeBowl] = React.useState( initialValues.CompositeBowl ); const [CompositeOwner, setCompositeOwner] = React.useState( initialValues.CompositeOwner ); const [CompositeToys, setCompositeToys] = React.useState( initialValues.CompositeToys ); const [CompositeVets, setCompositeVets] = React.useState( initialValues.CompositeVets ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = compositeDogRecord ? { ...initialValues, ...compositeDogRecord, CompositeBowl, CompositeOwner, CompositeToys: linkedCompositeToys, CompositeVets: linkedCompositeVets, } : initialValues; setName(cleanValues.name); setDescription(cleanValues.description); setCompositeBowl(cleanValues.CompositeBowl); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); setCompositeOwner(cleanValues.CompositeOwner); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); setCompositeToys(cleanValues.CompositeToys ?? []); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); setCompositeVets(cleanValues.CompositeVets ?? []); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); setErrors({}); }; const [compositeDogRecord, setCompositeDogRecord] = React.useState( compositeDogModelProp ); const [linkedCompositeToys, setLinkedCompositeToys] = React.useState([]); const canUnlinkCompositeToys = true; const [linkedCompositeVets, setLinkedCompositeVets] = React.useState([]); const canUnlinkCompositeVets = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(CompositeDog, idProp) : compositeDogModelProp; setCompositeDogRecord(record); const CompositeBowlRecord = record ? await record.CompositeBowl : undefined; setCompositeBowl(CompositeBowlRecord); const CompositeOwnerRecord = record ? await record.CompositeOwner : undefined; setCompositeOwner(CompositeOwnerRecord); const linkedCompositeToys = record ? await record.CompositeToys.toArray() : []; setLinkedCompositeToys(linkedCompositeToys); const linkedCompositeVets = record ? await Promise.all( ( await record.CompositeVets.toArray() ).map((r) => { return r.compositeVet; }) ) : []; setLinkedCompositeVets(linkedCompositeVets); }; queryData(); }, [idProp, compositeDogModelProp]); React.useEffect(resetStateValues, [ compositeDogRecord, CompositeBowl, CompositeOwner, linkedCompositeToys, linkedCompositeVets, ]); const [ currentCompositeBowlDisplayValue, setCurrentCompositeBowlDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeBowlValue, setCurrentCompositeBowlValue] = React.useState(undefined); const CompositeBowlRef = React.createRef(); const [ currentCompositeOwnerDisplayValue, setCurrentCompositeOwnerDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeOwnerValue, setCurrentCompositeOwnerValue] = React.useState(undefined); const CompositeOwnerRef = React.createRef(); const [ currentCompositeToysDisplayValue, setCurrentCompositeToysDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeToysValue, setCurrentCompositeToysValue] = React.useState(undefined); const CompositeToysRef = React.createRef(); const [ currentCompositeVetsDisplayValue, setCurrentCompositeVetsDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeVetsValue, setCurrentCompositeVetsValue] = React.useState(undefined); const CompositeVetsRef = React.createRef(); const getIDValue = { CompositeBowl: (r) => JSON.stringify({ shape: r?.shape, size: r?.size }), CompositeOwner: (r) => JSON.stringify({ lastName: r?.lastName, firstName: r?.firstName }), CompositeToys: (r) => JSON.stringify({ kind: r?.kind, color: r?.color }), CompositeVets: (r) => JSON.stringify({ specialty: r?.specialty, city: r?.city }), }; const CompositeBowlIdSet = new Set( Array.isArray(CompositeBowl) ? CompositeBowl.map((r) => getIDValue.CompositeBowl?.(r)) : getIDValue.CompositeBowl?.(CompositeBowl) ); const CompositeOwnerIdSet = new Set( Array.isArray(CompositeOwner) ? CompositeOwner.map((r) => getIDValue.CompositeOwner?.(r)) : getIDValue.CompositeOwner?.(CompositeOwner) ); const CompositeToysIdSet = new Set( Array.isArray(CompositeToys) ? CompositeToys.map((r) => getIDValue.CompositeToys?.(r)) : getIDValue.CompositeToys?.(CompositeToys) ); const CompositeVetsIdSet = new Set( Array.isArray(CompositeVets) ? CompositeVets.map((r) => getIDValue.CompositeVets?.(r)) : getIDValue.CompositeVets?.(CompositeVets) ); const compositeBowlRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeBowl0, }).items; const compositeOwnerRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeOwner0, }).items; const compositeToyRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeToy, }).items; const compositeVetRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeVet, }).items; const getDisplayValue = { CompositeBowl: (r) => \`\${r?.shape}\${\\"-\\"}\${r?.size}\`, CompositeOwner: (r) => \`\${r?.lastName}\${\\"-\\"}\${r?.firstName}\`, CompositeToys: (r) => \`\${r?.kind}\${\\"-\\"}\${r?.color}\`, CompositeVets: (r) => \`\${r?.specialty}\${\\"-\\"}\${r?.city}\`, }; const validations = { name: [{ type: \\"Required\\" }], description: [{ type: \\"Required\\" }], CompositeBowl: [], CompositeOwner: [], CompositeToys: [], CompositeVets: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const compositeOwnerToUnlink = await compositeDogRecord.CompositeOwner; if (compositeOwnerToUnlink) { promises.push( DataStore.save( CompositeOwner0.copyOf(compositeOwnerToUnlink, (updated) => { updated.CompositeDog = undefined; updated.compositeOwnerCompositeDogName = undefined; updated.compositeOwnerCompositeDogDescription = undefined; }) ) ); } const compositeOwnerToLink = modelFields.CompositeOwner; if (compositeOwnerToLink) { promises.push( DataStore.save( CompositeOwner0.copyOf(compositeOwnerToLink, (updated) => { updated.CompositeDog = compositeDogRecord; }) ) ); const compositeDogToUnlink = await compositeOwnerToLink.CompositeDog; if (compositeDogToUnlink) { promises.push( DataStore.save( CompositeDog.copyOf(compositeDogToUnlink, (updated) => { updated.CompositeOwner = undefined; updated.compositeDogCompositeOwnerLastName = undefined; updated.compositeDogCompositeOwnerFirstName = undefined; }) ) ); } } const compositeToysToLink = []; const compositeToysToUnLink = []; const compositeToysSet = new Set(); const linkedCompositeToysSet = new Set(); CompositeToys.forEach((r) => compositeToysSet.add(getIDValue.CompositeToys?.(r)) ); linkedCompositeToys.forEach((r) => linkedCompositeToysSet.add(getIDValue.CompositeToys?.(r)) ); linkedCompositeToys.forEach((r) => { if (!compositeToysSet.has(getIDValue.CompositeToys?.(r))) { compositeToysToUnLink.push(r); } }); CompositeToys.forEach((r) => { if (!linkedCompositeToysSet.has(getIDValue.CompositeToys?.(r))) { compositeToysToLink.push(r); } }); compositeToysToUnLink.forEach((original) => { if (!canUnlinkCompositeToys) { throw Error( \`CompositeToy \${original.kind} cannot be unlinked from CompositeDog because compositeDogCompositeToysName is a required field.\` ); } promises.push( DataStore.save( CompositeToy.copyOf(original, (updated) => { updated.compositeDogCompositeToysName = null; updated.compositeDogCompositeToysDescription = null; }) ) ); }); compositeToysToLink.forEach((original) => { promises.push( DataStore.save( CompositeToy.copyOf(original, (updated) => { updated.compositeDogCompositeToysName = compositeDogRecord.name; updated.compositeDogCompositeToysDescription = compositeDogRecord.description; }) ) ); }); const compositeVetsToLinkMap = new Map(); const compositeVetsToUnLinkMap = new Map(); const compositeVetsMap = new Map(); const linkedCompositeVetsMap = new Map(); CompositeVets.forEach((r) => { const count = compositeVetsMap.get(getIDValue.CompositeVets?.(r)); const newCount = count ? count + 1 : 1; compositeVetsMap.set(getIDValue.CompositeVets?.(r), newCount); }); linkedCompositeVets.forEach((r) => { const count = linkedCompositeVetsMap.get( getIDValue.CompositeVets?.(r) ); const newCount = count ? count + 1 : 1; linkedCompositeVetsMap.set(getIDValue.CompositeVets?.(r), newCount); }); linkedCompositeVetsMap.forEach((count, id) => { const newCount = compositeVetsMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { compositeVetsToUnLinkMap.set(id, diffCount); } } else { compositeVetsToUnLinkMap.set(id, count); } }); compositeVetsMap.forEach((count, id) => { const originalCount = linkedCompositeVetsMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { compositeVetsToLinkMap.set(id, diffCount); } } else { compositeVetsToLinkMap.set(id, count); } }); compositeVetsToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const compositeDogCompositeVetRecords = await DataStore.query( CompositeDogCompositeVet, (r) => r.and((r) => { return [ r.compositeVetSpecialty.eq(recordKeys.specialty), r.compositeVetcity.eq(recordKeys.city), r.compositeDogName.eq(compositeDogRecord.name), r.compositeDogdescription.eq( compositeDogRecord.description ), ]; }) ); for (let i = 0; i < count; i++) { promises.push( DataStore.delete(compositeDogCompositeVetRecords[i]) ); } }); compositeVetsToLinkMap.forEach((count, id) => { const compositeVetToLink = compositeVetRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( DataStore.save( new CompositeDogCompositeVet({ compositeDog: compositeDogRecord, compositeVet: compositeVetToLink, }) ) ); } }); const modelFieldsToSave = { name: modelFields.name, description: modelFields.description, CompositeBowl: modelFields.CompositeBowl, CompositeOwner: modelFields.CompositeOwner, }; promises.push( DataStore.save( CompositeDog.copyOf(compositeDogRecord, (updated) => { Object.assign(updated, modelFieldsToSave); if (!modelFieldsToSave.CompositeBowl) { updated.compositeDogCompositeBowlShape = undefined; updated.compositeDogCompositeBowlSize = undefined; } if (!modelFieldsToSave.CompositeOwner) { updated.compositeDogCompositeOwnerLastName = undefined; updated.compositeDogCompositeOwnerFirstName = undefined; } }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateCompositeDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"Description\\" isRequired={true} isReadOnly={true} value={description} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, description: value, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.description ?? value; } if (errors.description?.hasError) { runValidationTasks(\\"description\\", value); } setDescription(value); }} onBlur={() => runValidationTasks(\\"description\\", description)} errorMessage={errors.description?.errorMessage} hasError={errors.description?.hasError} {...getOverrideProps(overrides, \\"description\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl: value, CompositeOwner, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeBowl ?? value; } setCompositeBowl(value); setCurrentCompositeBowlValue(undefined); setCurrentCompositeBowlDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeBowlValue} label={\\"Composite bowl\\"} items={CompositeBowl ? [CompositeBowl] : []} hasError={errors?.CompositeBowl?.hasError} errorMessage={errors?.CompositeBowl?.errorMessage} getBadgeText={getDisplayValue.CompositeBowl} setFieldValue={(model) => { setCurrentCompositeBowlDisplayValue( model ? getDisplayValue.CompositeBowl(model) : \\"\\" ); setCurrentCompositeBowlValue(model); }} inputFieldRef={CompositeBowlRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite bowl\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeBowl\\" value={currentCompositeBowlDisplayValue} options={compositeBowlRecords .filter( (r) => !CompositeBowlIdSet.has(getIDValue.CompositeBowl?.(r)) ) .map((r) => ({ id: getIDValue.CompositeBowl?.(r), label: getDisplayValue.CompositeBowl?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeBowlValue( compositeBowlRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeBowlDisplayValue(label); runValidationTasks(\\"CompositeBowl\\", label); }} onClear={() => { setCurrentCompositeBowlDisplayValue(\\"\\"); }} defaultValue={CompositeBowl} onChange={(e) => { let { value } = e.target; if (errors.CompositeBowl?.hasError) { runValidationTasks(\\"CompositeBowl\\", value); } setCurrentCompositeBowlDisplayValue(value); setCurrentCompositeBowlValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeBowl\\", currentCompositeBowlDisplayValue ) } errorMessage={errors.CompositeBowl?.errorMessage} hasError={errors.CompositeBowl?.hasError} ref={CompositeBowlRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeBowl\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner: value, CompositeToys, CompositeVets, }; const result = onChange(modelFields); value = result?.CompositeOwner ?? value; } setCompositeOwner(value); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeOwnerValue} label={\\"Composite owner\\"} items={CompositeOwner ? [CompositeOwner] : []} hasError={errors?.CompositeOwner?.hasError} errorMessage={errors?.CompositeOwner?.errorMessage} getBadgeText={getDisplayValue.CompositeOwner} setFieldValue={(model) => { setCurrentCompositeOwnerDisplayValue( model ? getDisplayValue.CompositeOwner(model) : \\"\\" ); setCurrentCompositeOwnerValue(model); }} inputFieldRef={CompositeOwnerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite owner\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeOwner\\" value={currentCompositeOwnerDisplayValue} options={compositeOwnerRecords .filter( (r) => !CompositeOwnerIdSet.has(getIDValue.CompositeOwner?.(r)) ) .map((r) => ({ id: getIDValue.CompositeOwner?.(r), label: getDisplayValue.CompositeOwner?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeOwnerValue( compositeOwnerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeOwnerDisplayValue(label); runValidationTasks(\\"CompositeOwner\\", label); }} onClear={() => { setCurrentCompositeOwnerDisplayValue(\\"\\"); }} defaultValue={CompositeOwner} onChange={(e) => { let { value } = e.target; if (errors.CompositeOwner?.hasError) { runValidationTasks(\\"CompositeOwner\\", value); } setCurrentCompositeOwnerDisplayValue(value); setCurrentCompositeOwnerValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeOwner\\", currentCompositeOwnerDisplayValue ) } errorMessage={errors.CompositeOwner?.errorMessage} hasError={errors.CompositeOwner?.hasError} ref={CompositeOwnerRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeOwner\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys: values, CompositeVets, }; const result = onChange(modelFields); values = result?.CompositeToys ?? values; } setCompositeToys(values); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeToysValue} label={\\"Composite toys\\"} items={CompositeToys} hasError={errors?.CompositeToys?.hasError} errorMessage={errors?.CompositeToys?.errorMessage} getBadgeText={getDisplayValue.CompositeToys} setFieldValue={(model) => { setCurrentCompositeToysDisplayValue( model ? getDisplayValue.CompositeToys(model) : \\"\\" ); setCurrentCompositeToysValue(model); }} inputFieldRef={CompositeToysRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite toys\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeToy\\" value={currentCompositeToysDisplayValue} options={compositeToyRecords .filter( (r) => !CompositeToysIdSet.has(getIDValue.CompositeToys?.(r)) ) .map((r) => ({ id: getIDValue.CompositeToys?.(r), label: getDisplayValue.CompositeToys?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeToysValue( compositeToyRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeToysDisplayValue(label); runValidationTasks(\\"CompositeToys\\", label); }} onClear={() => { setCurrentCompositeToysDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeToys?.hasError) { runValidationTasks(\\"CompositeToys\\", value); } setCurrentCompositeToysDisplayValue(value); setCurrentCompositeToysValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeToys\\", currentCompositeToysDisplayValue ) } errorMessage={errors.CompositeToys?.errorMessage} hasError={errors.CompositeToys?.hasError} ref={CompositeToysRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeToys\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeBowl, CompositeOwner, CompositeToys, CompositeVets: values, }; const result = onChange(modelFields); values = result?.CompositeVets ?? values; } setCompositeVets(values); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeVetsValue} label={\\"Composite vets\\"} items={CompositeVets} hasError={errors?.CompositeVets?.hasError} errorMessage={errors?.CompositeVets?.errorMessage} getBadgeText={getDisplayValue.CompositeVets} setFieldValue={(model) => { setCurrentCompositeVetsDisplayValue( model ? getDisplayValue.CompositeVets(model) : \\"\\" ); setCurrentCompositeVetsValue(model); }} inputFieldRef={CompositeVetsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite vets\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeVet\\" value={currentCompositeVetsDisplayValue} options={compositeVetRecords .filter( (r) => !CompositeVetsIdSet.has(getIDValue.CompositeVets?.(r)) ) .map((r) => ({ id: getIDValue.CompositeVets?.(r), label: getDisplayValue.CompositeVets?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeVetsValue( compositeVetRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeVetsDisplayValue(label); runValidationTasks(\\"CompositeVets\\", label); }} onClear={() => { setCurrentCompositeVetsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeVets?.hasError) { runValidationTasks(\\"CompositeVets\\", value); } setCurrentCompositeVetsDisplayValue(value); setCurrentCompositeVetsValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeVets\\", currentCompositeVetsDisplayValue ) } errorMessage={errors.CompositeVets?.errorMessage} hasError={errors.CompositeVets?.hasError} ref={CompositeVetsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeVets\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || compositeDogModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || compositeDogModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render an update form for model with composite keys 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeDog, CompositeBowl as CompositeBowl0, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateCompositeDogFormInputValues = { name?: string; description?: string; CompositeBowl?: CompositeBowl0; CompositeOwner?: CompositeOwner0; CompositeToys?: CompositeToy[]; CompositeVets?: CompositeVet[]; }; export declare type UpdateCompositeDogFormValidationValues = { name?: ValidationFunction<string>; description?: ValidationFunction<string>; CompositeBowl?: ValidationFunction<CompositeBowl0>; CompositeOwner?: ValidationFunction<CompositeOwner0>; CompositeToys?: ValidationFunction<CompositeToy>; CompositeVets?: ValidationFunction<CompositeVet>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateCompositeDogFormOverridesProps = { UpdateCompositeDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; description?: PrimitiveOverrideProps<TextFieldProps>; CompositeBowl?: PrimitiveOverrideProps<AutocompleteProps>; CompositeOwner?: PrimitiveOverrideProps<AutocompleteProps>; CompositeToys?: PrimitiveOverrideProps<AutocompleteProps>; CompositeVets?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateCompositeDogFormProps = React.PropsWithChildren<{ overrides?: UpdateCompositeDogFormOverridesProps | undefined | null; } & { id?: { name: string; description: string; }; compositeDog?: CompositeDog; onSubmit?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues; onSuccess?: (fields: UpdateCompositeDogFormInputValues) => void; onError?: (fields: UpdateCompositeDogFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues; onValidate?: UpdateCompositeDogFormValidationValues; } & React.CSSProperties>; export default function UpdateCompositeDogForm(props: UpdateCompositeDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render an update form for model with cpk 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { CPKTeacher, CPKStudent as CPKStudent0, CPKClass, CPKProject, CPKTeacherCPKClass, } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateCPKTeacherForm(props) { const { specialTeacherId: specialTeacherIdProp, cPKTeacher: cPKTeacherModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { specialTeacherId: \\"\\", CPKStudent: undefined, CPKClasses: [], CPKProjects: [], }; const [specialTeacherId, setSpecialTeacherId] = React.useState( initialValues.specialTeacherId ); const [CPKStudent, setCPKStudent] = React.useState(initialValues.CPKStudent); const [CPKClasses, setCPKClasses] = React.useState(initialValues.CPKClasses); const [CPKProjects, setCPKProjects] = React.useState( initialValues.CPKProjects ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = cPKTeacherRecord ? { ...initialValues, ...cPKTeacherRecord, CPKStudent, CPKClasses: linkedCPKClasses, CPKProjects: linkedCPKProjects, } : initialValues; setSpecialTeacherId(cleanValues.specialTeacherId); setCPKStudent(cleanValues.CPKStudent); setCurrentCPKStudentValue(undefined); setCurrentCPKStudentDisplayValue(\\"\\"); setCPKClasses(cleanValues.CPKClasses ?? []); setCurrentCPKClassesValue(undefined); setCurrentCPKClassesDisplayValue(\\"\\"); setCPKProjects(cleanValues.CPKProjects ?? []); setCurrentCPKProjectsValue(undefined); setCurrentCPKProjectsDisplayValue(\\"\\"); setErrors({}); }; const [cPKTeacherRecord, setCPKTeacherRecord] = React.useState(cPKTeacherModelProp); const [linkedCPKClasses, setLinkedCPKClasses] = React.useState([]); const canUnlinkCPKClasses = false; const [linkedCPKProjects, setLinkedCPKProjects] = React.useState([]); const canUnlinkCPKProjects = true; React.useEffect(() => { const queryData = async () => { const record = specialTeacherIdProp ? await DataStore.query(CPKTeacher, specialTeacherIdProp) : cPKTeacherModelProp; setCPKTeacherRecord(record); const CPKStudentRecord = record ? await record.CPKStudent : undefined; setCPKStudent(CPKStudentRecord); const linkedCPKClasses = record ? await Promise.all( ( await record.CPKClasses.toArray() ).map((r) => { return r.cpkClass; }) ) : []; setLinkedCPKClasses(linkedCPKClasses); const linkedCPKProjects = record ? await record.CPKProjects.toArray() : []; setLinkedCPKProjects(linkedCPKProjects); }; queryData(); }, [specialTeacherIdProp, cPKTeacherModelProp]); React.useEffect(resetStateValues, [ cPKTeacherRecord, CPKStudent, linkedCPKClasses, linkedCPKProjects, ]); const [currentCPKStudentDisplayValue, setCurrentCPKStudentDisplayValue] = React.useState(\\"\\"); const [currentCPKStudentValue, setCurrentCPKStudentValue] = React.useState(undefined); const CPKStudentRef = React.createRef(); const [currentCPKClassesDisplayValue, setCurrentCPKClassesDisplayValue] = React.useState(\\"\\"); const [currentCPKClassesValue, setCurrentCPKClassesValue] = React.useState(undefined); const CPKClassesRef = React.createRef(); const [currentCPKProjectsDisplayValue, setCurrentCPKProjectsDisplayValue] = React.useState(\\"\\"); const [currentCPKProjectsValue, setCurrentCPKProjectsValue] = React.useState(undefined); const CPKProjectsRef = React.createRef(); const getIDValue = { CPKStudent: (r) => JSON.stringify({ specialStudentId: r?.specialStudentId }), CPKClasses: (r) => JSON.stringify({ specialClassId: r?.specialClassId }), CPKProjects: (r) => JSON.stringify({ specialProjectId: r?.specialProjectId }), }; const CPKStudentIdSet = new Set( Array.isArray(CPKStudent) ? CPKStudent.map((r) => getIDValue.CPKStudent?.(r)) : getIDValue.CPKStudent?.(CPKStudent) ); const CPKClassesIdSet = new Set( Array.isArray(CPKClasses) ? CPKClasses.map((r) => getIDValue.CPKClasses?.(r)) : getIDValue.CPKClasses?.(CPKClasses) ); const CPKProjectsIdSet = new Set( Array.isArray(CPKProjects) ? CPKProjects.map((r) => getIDValue.CPKProjects?.(r)) : getIDValue.CPKProjects?.(CPKProjects) ); const cPKStudentRecords = useDataStoreBinding({ type: \\"collection\\", model: CPKStudent0, }).items; const cPKClassRecords = useDataStoreBinding({ type: \\"collection\\", model: CPKClass, }).items; const cPKProjectRecords = useDataStoreBinding({ type: \\"collection\\", model: CPKProject, }).items; const getDisplayValue = { CPKStudent: (r) => r?.specialStudentId, CPKClasses: (r) => r?.specialClassId, CPKProjects: (r) => r?.specialProjectId, }; const validations = { specialTeacherId: [{ type: \\"Required\\" }], CPKStudent: [], CPKClasses: [], CPKProjects: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { specialTeacherId, CPKStudent, CPKClasses, CPKProjects, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const cPKClassesToLinkMap = new Map(); const cPKClassesToUnLinkMap = new Map(); const cPKClassesMap = new Map(); const linkedCPKClassesMap = new Map(); CPKClasses.forEach((r) => { const count = cPKClassesMap.get(getIDValue.CPKClasses?.(r)); const newCount = count ? count + 1 : 1; cPKClassesMap.set(getIDValue.CPKClasses?.(r), newCount); }); linkedCPKClasses.forEach((r) => { const count = linkedCPKClassesMap.get(getIDValue.CPKClasses?.(r)); const newCount = count ? count + 1 : 1; linkedCPKClassesMap.set(getIDValue.CPKClasses?.(r), newCount); }); linkedCPKClassesMap.forEach((count, id) => { const newCount = cPKClassesMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { cPKClassesToUnLinkMap.set(id, diffCount); } } else { cPKClassesToUnLinkMap.set(id, count); } }); cPKClassesMap.forEach((count, id) => { const originalCount = linkedCPKClassesMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { cPKClassesToLinkMap.set(id, diffCount); } } else { cPKClassesToLinkMap.set(id, count); } }); cPKClassesToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const cPKTeacherCPKClassRecords = await DataStore.query( CPKTeacherCPKClass, (r) => r.and((r) => { return [ r.cPKClassSpecialClassId.eq(recordKeys.specialClassId), r.cPKTeacherSpecialTeacherId.eq( cPKTeacherRecord.specialTeacherId ), ]; }) ); for (let i = 0; i < count; i++) { promises.push(DataStore.delete(cPKTeacherCPKClassRecords[i])); } }); cPKClassesToLinkMap.forEach((count, id) => { const cPKClassToLink = cPKClassRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( DataStore.save( new CPKTeacherCPKClass({ cpkTeacher: cPKTeacherRecord, cpkClass: cPKClassToLink, }) ) ); } }); const cPKProjectsToLink = []; const cPKProjectsToUnLink = []; const cPKProjectsSet = new Set(); const linkedCPKProjectsSet = new Set(); CPKProjects.forEach((r) => cPKProjectsSet.add(getIDValue.CPKProjects?.(r)) ); linkedCPKProjects.forEach((r) => linkedCPKProjectsSet.add(getIDValue.CPKProjects?.(r)) ); linkedCPKProjects.forEach((r) => { if (!cPKProjectsSet.has(getIDValue.CPKProjects?.(r))) { cPKProjectsToUnLink.push(r); } }); CPKProjects.forEach((r) => { if (!linkedCPKProjectsSet.has(getIDValue.CPKProjects?.(r))) { cPKProjectsToLink.push(r); } }); cPKProjectsToUnLink.forEach((original) => { if (!canUnlinkCPKProjects) { throw Error( \`CPKProject \${original.specialProjectId} cannot be unlinked from CPKTeacher because cPKTeacherID is a required field.\` ); } promises.push( DataStore.save( CPKProject.copyOf(original, (updated) => { updated.cPKTeacherID = null; }) ) ); }); cPKProjectsToLink.forEach((original) => { promises.push( DataStore.save( CPKProject.copyOf(original, (updated) => { updated.cPKTeacherID = cPKTeacherRecord.specialTeacherId; }) ) ); }); const modelFieldsToSave = { specialTeacherId: modelFields.specialTeacherId, CPKStudent: modelFields.CPKStudent, }; promises.push( DataStore.save( CPKTeacher.copyOf(cPKTeacherRecord, (updated) => { Object.assign(updated, modelFieldsToSave); if (!modelFieldsToSave.CPKStudent) { updated.cPKTeacherCPKStudentSpecialStudentId = undefined; } }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateCPKTeacherForm\\")} {...rest} > <TextField label=\\"Special teacher id\\" isRequired={true} isReadOnly={true} value={specialTeacherId} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { specialTeacherId: value, CPKStudent, CPKClasses, CPKProjects, }; const result = onChange(modelFields); value = result?.specialTeacherId ?? value; } if (errors.specialTeacherId?.hasError) { runValidationTasks(\\"specialTeacherId\\", value); } setSpecialTeacherId(value); }} onBlur={() => runValidationTasks(\\"specialTeacherId\\", specialTeacherId)} errorMessage={errors.specialTeacherId?.errorMessage} hasError={errors.specialTeacherId?.hasError} {...getOverrideProps(overrides, \\"specialTeacherId\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { specialTeacherId, CPKStudent: value, CPKClasses, CPKProjects, }; const result = onChange(modelFields); value = result?.CPKStudent ?? value; } setCPKStudent(value); setCurrentCPKStudentValue(undefined); setCurrentCPKStudentDisplayValue(\\"\\"); }} currentFieldValue={currentCPKStudentValue} label={\\"Cpk student\\"} items={CPKStudent ? [CPKStudent] : []} hasError={errors?.CPKStudent?.hasError} errorMessage={errors?.CPKStudent?.errorMessage} getBadgeText={getDisplayValue.CPKStudent} setFieldValue={(model) => { setCurrentCPKStudentDisplayValue( model ? getDisplayValue.CPKStudent(model) : \\"\\" ); setCurrentCPKStudentValue(model); }} inputFieldRef={CPKStudentRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk student\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKStudent\\" value={currentCPKStudentDisplayValue} options={cPKStudentRecords .filter((r) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(r))) .map((r) => ({ id: getIDValue.CPKStudent?.(r), label: getDisplayValue.CPKStudent?.(r), }))} onSelect={({ id, label }) => { setCurrentCPKStudentValue( cPKStudentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKStudentDisplayValue(label); runValidationTasks(\\"CPKStudent\\", label); }} onClear={() => { setCurrentCPKStudentDisplayValue(\\"\\"); }} defaultValue={CPKStudent} onChange={(e) => { let { value } = e.target; if (errors.CPKStudent?.hasError) { runValidationTasks(\\"CPKStudent\\", value); } setCurrentCPKStudentDisplayValue(value); setCurrentCPKStudentValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKStudent\\", currentCPKStudentDisplayValue) } errorMessage={errors.CPKStudent?.errorMessage} hasError={errors.CPKStudent?.hasError} ref={CPKStudentRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKStudent\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { specialTeacherId, CPKStudent, CPKClasses: values, CPKProjects, }; const result = onChange(modelFields); values = result?.CPKClasses ?? values; } setCPKClasses(values); setCurrentCPKClassesValue(undefined); setCurrentCPKClassesDisplayValue(\\"\\"); }} currentFieldValue={currentCPKClassesValue} label={\\"Cpk classes\\"} items={CPKClasses} hasError={errors?.CPKClasses?.hasError} errorMessage={errors?.CPKClasses?.errorMessage} getBadgeText={getDisplayValue.CPKClasses} setFieldValue={(model) => { setCurrentCPKClassesDisplayValue( model ? getDisplayValue.CPKClasses(model) : \\"\\" ); setCurrentCPKClassesValue(model); }} inputFieldRef={CPKClassesRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk classes\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKClass\\" value={currentCPKClassesDisplayValue} options={cPKClassRecords .filter((r) => !CPKClassesIdSet.has(getIDValue.CPKClasses?.(r))) .map((r) => ({ id: getIDValue.CPKClasses?.(r), label: getDisplayValue.CPKClasses?.(r), }))} onSelect={({ id, label }) => { setCurrentCPKClassesValue( cPKClassRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKClassesDisplayValue(label); runValidationTasks(\\"CPKClasses\\", label); }} onClear={() => { setCurrentCPKClassesDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CPKClasses?.hasError) { runValidationTasks(\\"CPKClasses\\", value); } setCurrentCPKClassesDisplayValue(value); setCurrentCPKClassesValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKClasses\\", currentCPKClassesDisplayValue) } errorMessage={errors.CPKClasses?.errorMessage} hasError={errors.CPKClasses?.hasError} ref={CPKClassesRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKClasses\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { specialTeacherId, CPKStudent, CPKClasses, CPKProjects: values, }; const result = onChange(modelFields); values = result?.CPKProjects ?? values; } setCPKProjects(values); setCurrentCPKProjectsValue(undefined); setCurrentCPKProjectsDisplayValue(\\"\\"); }} currentFieldValue={currentCPKProjectsValue} label={\\"Cpk projects\\"} items={CPKProjects} hasError={errors?.CPKProjects?.hasError} errorMessage={errors?.CPKProjects?.errorMessage} getBadgeText={getDisplayValue.CPKProjects} setFieldValue={(model) => { setCurrentCPKProjectsDisplayValue( model ? getDisplayValue.CPKProjects(model) : \\"\\" ); setCurrentCPKProjectsValue(model); }} inputFieldRef={CPKProjectsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cpk projects\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CPKProject\\" value={currentCPKProjectsDisplayValue} options={cPKProjectRecords .filter((r) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(r))) .map((r) => ({ id: getIDValue.CPKProjects?.(r), label: getDisplayValue.CPKProjects?.(r), }))} onSelect={({ id, label }) => { setCurrentCPKProjectsValue( cPKProjectRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCPKProjectsDisplayValue(label); runValidationTasks(\\"CPKProjects\\", label); }} onClear={() => { setCurrentCPKProjectsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CPKProjects?.hasError) { runValidationTasks(\\"CPKProjects\\", value); } setCurrentCPKProjectsDisplayValue(value); setCurrentCPKProjectsValue(undefined); }} onBlur={() => runValidationTasks(\\"CPKProjects\\", currentCPKProjectsDisplayValue) } errorMessage={errors.CPKProjects?.errorMessage} hasError={errors.CPKProjects?.hasError} ref={CPKProjectsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CPKProjects\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(specialTeacherIdProp || cPKTeacherModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(specialTeacherIdProp || cPKTeacherModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render an update form for model with cpk 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CPKTeacher, CPKStudent as CPKStudent0, CPKClass, CPKProject } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateCPKTeacherFormInputValues = { specialTeacherId?: string; CPKStudent?: CPKStudent0; CPKClasses?: CPKClass[]; CPKProjects?: CPKProject[]; }; export declare type UpdateCPKTeacherFormValidationValues = { specialTeacherId?: ValidationFunction<string>; CPKStudent?: ValidationFunction<CPKStudent0>; CPKClasses?: ValidationFunction<CPKClass>; CPKProjects?: ValidationFunction<CPKProject>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateCPKTeacherFormOverridesProps = { UpdateCPKTeacherFormGrid?: PrimitiveOverrideProps<GridProps>; specialTeacherId?: PrimitiveOverrideProps<TextFieldProps>; CPKStudent?: PrimitiveOverrideProps<AutocompleteProps>; CPKClasses?: PrimitiveOverrideProps<AutocompleteProps>; CPKProjects?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateCPKTeacherFormProps = React.PropsWithChildren<{ overrides?: UpdateCPKTeacherFormOverridesProps | undefined | null; } & { specialTeacherId?: string; cPKTeacher?: CPKTeacher; onSubmit?: (fields: UpdateCPKTeacherFormInputValues) => UpdateCPKTeacherFormInputValues; onSuccess?: (fields: UpdateCPKTeacherFormInputValues) => void; onError?: (fields: UpdateCPKTeacherFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateCPKTeacherFormInputValues) => UpdateCPKTeacherFormInputValues; onValidate?: UpdateCPKTeacherFormValidationValues; } & React.CSSProperties>; export default function UpdateCPKTeacherForm(props: UpdateCPKTeacherFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render happy path nested json fields for create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Heading, Icon, ScrollView, SwitchField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function HappyPathJSONCreate(props) { const { onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { basics: {}, favoriteThings: {}, active: false, enabled: false, }; const [basics, setBasics] = React.useState(initialValues.basics); const [favoriteThings, setFavoriteThings] = React.useState( initialValues.favoriteThings ); const [active, setActive] = React.useState(initialValues.active); const [enabled, setEnabled] = React.useState(initialValues.enabled); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setBasics(initialValues.basics); setFavoriteThings(initialValues.favoriteThings); setActive(initialValues.active); setEnabled(initialValues.enabled); setErrors({}); }; const [ currentFavoriteThingsAnimalsValue, setCurrentFavoriteThingsAnimalsValue, ] = React.useState(\\"\\"); const favoriteThingsAnimalsRef = React.createRef(); const validations = { \\"basics.firstName\\": [], \\"basics.emailAddress\\": [{ type: \\"Email\\" }], \\"favoriteThings.animals\\": [], \\"favoriteThings.month\\": [], \\"favoriteThings.number\\": [], active: [], enabled: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { basics, favoriteThings, active, enabled, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"HappyPathJSONCreate\\")} {...rest} > <Heading level={3} children=\\"Basics\\" {...getOverrideProps(overrides, \\"basics\\")} ></Heading> <TextField label=\\"First name\\" value={basics[\\"firstName\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics: { ...basics, firstName: value }, favoriteThings, active, enabled, }; const result = onChange(modelFields); value = result?.basics?.firstName ?? value; } if (errors[\\"basics.firstName\\"]?.hasError) { runValidationTasks(\\"basics.firstName\\", value); } setBasics({ ...basics, firstName: value }); }} onBlur={() => runValidationTasks(\\"basics.firstName\\", basics[\\"firstName\\"]) } errorMessage={errors[\\"basics.firstName\\"]?.errorMessage} hasError={errors[\\"basics.firstName\\"]?.hasError} {...getOverrideProps(overrides, \\"basics.firstName\\")} ></TextField> <TextField label=\\"Email address\\" value={basics[\\"emailAddress\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics: { ...basics, emailAddress: value }, favoriteThings, active, enabled, }; const result = onChange(modelFields); value = result?.basics?.emailAddress ?? value; } if (errors[\\"basics.emailAddress\\"]?.hasError) { runValidationTasks(\\"basics.emailAddress\\", value); } setBasics({ ...basics, emailAddress: value }); }} onBlur={() => runValidationTasks(\\"basics.emailAddress\\", basics[\\"emailAddress\\"]) } errorMessage={errors[\\"basics.emailAddress\\"]?.errorMessage} hasError={errors[\\"basics.emailAddress\\"]?.hasError} {...getOverrideProps(overrides, \\"basics.emailAddress\\")} ></TextField> <Heading level={3} children=\\"Favorite things\\" {...getOverrideProps(overrides, \\"favoriteThings\\")} ></Heading> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, animals: values }, active, enabled, }; const result = onChange(modelFields); values = result?.favoriteThings?.animals ?? values; } setFavoriteThings({ ...favoriteThings, animals: values }); setCurrentFavoriteThingsAnimalsValue(\\"\\"); }} currentFieldValue={currentFavoriteThingsAnimalsValue} label={\\"Animals\\"} items={favoriteThings.animals ?? []} hasError={errors?.[\\"favoriteThings.animals\\"]?.hasError} errorMessage={errors?.[\\"favoriteThings.animals\\"]?.errorMessage} setFieldValue={setCurrentFavoriteThingsAnimalsValue} inputFieldRef={favoriteThingsAnimalsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Animals\\" value={currentFavoriteThingsAnimalsValue} onChange={(e) => { let { value } = e.target; if (errors[\\"favoriteThings.animals\\"]?.hasError) { runValidationTasks(\\"favoriteThings.animals\\", value); } setCurrentFavoriteThingsAnimalsValue(value); }} onBlur={() => runValidationTasks( \\"favoriteThings.animals\\", currentFavoriteThingsAnimalsValue ) } errorMessage={errors[\\"favoriteThings.animals\\"]?.errorMessage} hasError={errors[\\"favoriteThings.animals\\"]?.hasError} ref={favoriteThingsAnimalsRef} labelHidden={true} {...getOverrideProps(overrides, \\"favoriteThings.animals\\")} ></TextField> </ArrayField> <TextField label=\\"Month\\" value={favoriteThings[\\"month\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, month: value }, active, enabled, }; const result = onChange(modelFields); value = result?.favoriteThings?.month ?? value; } if (errors[\\"favoriteThings.month\\"]?.hasError) { runValidationTasks(\\"favoriteThings.month\\", value); } setFavoriteThings({ ...favoriteThings, month: value }); }} onBlur={() => runValidationTasks(\\"favoriteThings.month\\", favoriteThings[\\"month\\"]) } errorMessage={errors[\\"favoriteThings.month\\"]?.errorMessage} hasError={errors[\\"favoriteThings.month\\"]?.hasError} {...getOverrideProps(overrides, \\"favoriteThings.month\\")} ></TextField> <TextField label=\\"Number\\" type=\\"number\\" step=\\"any\\" value={favoriteThings[\\"number\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, number: value }, active, enabled, }; const result = onChange(modelFields); value = result?.favoriteThings?.number ?? value; } if (errors[\\"favoriteThings.number\\"]?.hasError) { runValidationTasks(\\"favoriteThings.number\\", value); } setFavoriteThings({ ...favoriteThings, number: value }); }} onBlur={() => runValidationTasks(\\"favoriteThings.number\\", favoriteThings[\\"number\\"]) } errorMessage={errors[\\"favoriteThings.number\\"]?.errorMessage} hasError={errors[\\"favoriteThings.number\\"]?.hasError} {...getOverrideProps(overrides, \\"favoriteThings.number\\")} ></TextField> <Divider orientation=\\"horizontal\\" {...getOverrideProps(overrides, \\"activeSectionalElement\\")} ></Divider> <SwitchField label=\\"Active\\" defaultChecked={false} isChecked={active} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { basics, favoriteThings, active: value, enabled, }; const result = onChange(modelFields); value = result?.active ?? value; } if (errors.active?.hasError) { runValidationTasks(\\"active\\", value); } setActive(value); }} onBlur={() => runValidationTasks(\\"active\\", active)} errorMessage={errors.active?.errorMessage} hasError={errors.active?.hasError} {...getOverrideProps(overrides, \\"active\\")} ></SwitchField> <SwitchField label=\\"Enabled\\" defaultChecked={false} isChecked={enabled} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { basics, favoriteThings, active, enabled: value, }; const result = onChange(modelFields); value = result?.enabled ?? value; } if (errors.enabled?.hasError) { runValidationTasks(\\"enabled\\", value); } setEnabled(value); }} onBlur={() => runValidationTasks(\\"enabled\\", enabled)} errorMessage={errors.enabled?.errorMessage} hasError={errors.enabled?.hasError} {...getOverrideProps(overrides, \\"enabled\\")} ></SwitchField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render happy path nested json fields for create form 2`] = ` "import * as React from \\"react\\"; import { DividerProps, GridProps, HeadingProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type HappyPathJSONCreateInputValues = { active?: boolean; enabled?: boolean; basics?: { firstName?: string; emailAddress?: string; }; favoriteThings?: { animals?: string[]; month?: string; number?: string; }; }; export declare type HappyPathJSONCreateValidationValues = { active?: ValidationFunction<boolean>; enabled?: ValidationFunction<boolean>; basics?: { firstName?: ValidationFunction<string>; emailAddress?: ValidationFunction<string>; }; favoriteThings?: { animals?: ValidationFunction<string>; month?: ValidationFunction<string>; number?: ValidationFunction<string>; }; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type HappyPathJSONCreateOverridesProps = { HappyPathJSONCreateGrid?: PrimitiveOverrideProps<GridProps>; basics?: PrimitiveOverrideProps<HeadingProps>; \\"basics.firstName\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"basics.emailAddress\\"?: PrimitiveOverrideProps<TextFieldProps>; favoriteThings?: PrimitiveOverrideProps<HeadingProps>; \\"favoriteThings.animals\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"favoriteThings.month\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"favoriteThings.number\\"?: PrimitiveOverrideProps<TextFieldProps>; activeSectionalElement?: PrimitiveOverrideProps<DividerProps>; active?: PrimitiveOverrideProps<SwitchFieldProps>; enabled?: PrimitiveOverrideProps<SwitchFieldProps>; } & EscapeHatchProps; export declare type HappyPathJSONCreateProps = React.PropsWithChildren<{ overrides?: HappyPathJSONCreateOverridesProps | undefined | null; } & { onSubmit: (fields: HappyPathJSONCreateInputValues) => void; onChange?: (fields: HappyPathJSONCreateInputValues) => HappyPathJSONCreateInputValues; onValidate?: HappyPathJSONCreateValidationValues; } & React.CSSProperties>; export default function HappyPathJSONCreate(props: HappyPathJSONCreateProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render happy path nested json fields for update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Heading, Icon, ScrollView, SwitchField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function HappyPathJSONUpdate(props) { const { initialData, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { basics: {}, favoriteThings: {}, active: false, enabled: false, }; const [basics, setBasics] = React.useState(initialValues.basics); const [favoriteThings, setFavoriteThings] = React.useState( initialValues.favoriteThings ); const [active, setActive] = React.useState(initialValues.active); const [enabled, setEnabled] = React.useState(initialValues.enabled); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = initialData ? { ...initialValues, ...initialData } : initialValues; setBasics(cleanValues.basics); setFavoriteThings(cleanValues.favoriteThings ?? []); setActive(cleanValues.active); setEnabled(cleanValues.enabled); setErrors({}); }; React.useEffect(resetStateValues, [initialData]); React.useEffect(() => { if (initialData) { setBasics(initialData.basics); setFavoriteThings(initialData.favoriteThings); setActive(initialData.active); setEnabled(initialData.enabled); } }, []); const [ currentFavoriteThingsAnimalsValue, setCurrentFavoriteThingsAnimalsValue, ] = React.useState(\\"\\"); const favoriteThingsAnimalsRef = React.createRef(); const validations = { \\"basics.firstName\\": [], \\"basics.emailAddress\\": [{ type: \\"Email\\" }], \\"favoriteThings.animals\\": [], \\"favoriteThings.month\\": [], \\"favoriteThings.number\\": [], active: [], enabled: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { basics, favoriteThings, active, enabled, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"HappyPathJSONUpdate\\")} {...rest} > <Heading level={3} children=\\"Basics\\" {...getOverrideProps(overrides, \\"basics\\")} ></Heading> <TextField label=\\"First name\\" value={basics[\\"firstName\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics: { ...basics, firstName: value }, favoriteThings, active, enabled, }; const result = onChange(modelFields); value = result?.basics?.firstName ?? value; } if (errors[\\"basics.firstName\\"]?.hasError) { runValidationTasks(\\"basics.firstName\\", value); } setBasics({ ...basics, firstName: value }); }} onBlur={() => runValidationTasks(\\"basics.firstName\\", basics[\\"firstName\\"]) } errorMessage={errors[\\"basics.firstName\\"]?.errorMessage} hasError={errors[\\"basics.firstName\\"]?.hasError} {...getOverrideProps(overrides, \\"basics.firstName\\")} ></TextField> <TextField label=\\"Email address\\" value={basics[\\"emailAddress\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics: { ...basics, emailAddress: value }, favoriteThings, active, enabled, }; const result = onChange(modelFields); value = result?.basics?.emailAddress ?? value; } if (errors[\\"basics.emailAddress\\"]?.hasError) { runValidationTasks(\\"basics.emailAddress\\", value); } setBasics({ ...basics, emailAddress: value }); }} onBlur={() => runValidationTasks(\\"basics.emailAddress\\", basics[\\"emailAddress\\"]) } errorMessage={errors[\\"basics.emailAddress\\"]?.errorMessage} hasError={errors[\\"basics.emailAddress\\"]?.hasError} {...getOverrideProps(overrides, \\"basics.emailAddress\\")} ></TextField> <Heading level={3} children=\\"Favorite things\\" {...getOverrideProps(overrides, \\"favoriteThings\\")} ></Heading> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, animals: values }, active, enabled, }; const result = onChange(modelFields); values = result?.favoriteThings?.animals ?? values; } setFavoriteThings({ ...favoriteThings, animals: values }); setCurrentFavoriteThingsAnimalsValue(\\"\\"); }} currentFieldValue={currentFavoriteThingsAnimalsValue} label={\\"Animals\\"} items={favoriteThings.animals ?? []} hasError={errors?.[\\"favoriteThings.animals\\"]?.hasError} errorMessage={errors?.[\\"favoriteThings.animals\\"]?.errorMessage} setFieldValue={setCurrentFavoriteThingsAnimalsValue} inputFieldRef={favoriteThingsAnimalsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Animals\\" value={currentFavoriteThingsAnimalsValue} onChange={(e) => { let { value } = e.target; if (errors[\\"favoriteThings.animals\\"]?.hasError) { runValidationTasks(\\"favoriteThings.animals\\", value); } setCurrentFavoriteThingsAnimalsValue(value); }} onBlur={() => runValidationTasks( \\"favoriteThings.animals\\", currentFavoriteThingsAnimalsValue ) } errorMessage={errors[\\"favoriteThings.animals\\"]?.errorMessage} hasError={errors[\\"favoriteThings.animals\\"]?.hasError} ref={favoriteThingsAnimalsRef} labelHidden={true} {...getOverrideProps(overrides, \\"favoriteThings.animals\\")} ></TextField> </ArrayField> <TextField label=\\"Month\\" value={favoriteThings[\\"month\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, month: value }, active, enabled, }; const result = onChange(modelFields); value = result?.favoriteThings?.month ?? value; } if (errors[\\"favoriteThings.month\\"]?.hasError) { runValidationTasks(\\"favoriteThings.month\\", value); } setFavoriteThings({ ...favoriteThings, month: value }); }} onBlur={() => runValidationTasks(\\"favoriteThings.month\\", favoriteThings[\\"month\\"]) } errorMessage={errors[\\"favoriteThings.month\\"]?.errorMessage} hasError={errors[\\"favoriteThings.month\\"]?.hasError} {...getOverrideProps(overrides, \\"favoriteThings.month\\")} ></TextField> <TextField label=\\"Number\\" type=\\"number\\" step=\\"any\\" value={favoriteThings[\\"number\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { basics, favoriteThings: { ...favoriteThings, number: value }, active, enabled, }; const result = onChange(modelFields); value = result?.favoriteThings?.number ?? value; } if (errors[\\"favoriteThings.number\\"]?.hasError) { runValidationTasks(\\"favoriteThings.number\\", value); } setFavoriteThings({ ...favoriteThings, number: value }); }} onBlur={() => runValidationTasks(\\"favoriteThings.number\\", favoriteThings[\\"number\\"]) } errorMessage={errors[\\"favoriteThings.number\\"]?.errorMessage} hasError={errors[\\"favoriteThings.number\\"]?.hasError} {...getOverrideProps(overrides, \\"favoriteThings.number\\")} ></TextField> <Divider orientation=\\"horizontal\\" {...getOverrideProps(overrides, \\"activeSectionalElement\\")} ></Divider> <SwitchField label=\\"Active\\" defaultChecked={false} isChecked={active} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { basics, favoriteThings, active: value, enabled, }; const result = onChange(modelFields); value = result?.active ?? value; } if (errors.active?.hasError) { runValidationTasks(\\"active\\", value); } setActive(value); }} onBlur={() => runValidationTasks(\\"active\\", active)} errorMessage={errors.active?.errorMessage} hasError={errors.active?.hasError} {...getOverrideProps(overrides, \\"active\\")} ></SwitchField> <SwitchField label=\\"Enabled\\" defaultChecked={false} isChecked={enabled} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { basics, favoriteThings, active, enabled: value, }; const result = onChange(modelFields); value = result?.enabled ?? value; } if (errors.enabled?.hasError) { runValidationTasks(\\"enabled\\", value); } setEnabled(value); }} onBlur={() => runValidationTasks(\\"enabled\\", enabled)} errorMessage={errors.enabled?.errorMessage} hasError={errors.enabled?.hasError} {...getOverrideProps(overrides, \\"enabled\\")} ></SwitchField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render happy path nested json fields for update form 2`] = ` "import * as React from \\"react\\"; import { DividerProps, GridProps, HeadingProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type HappyPathJSONUpdateInputValues = { active?: boolean; enabled?: boolean; basics?: { firstName?: string; emailAddress?: string; }; favoriteThings?: { animals?: string[]; month?: string; number?: string; }; }; export declare type HappyPathJSONUpdateValidationValues = { active?: ValidationFunction<boolean>; enabled?: ValidationFunction<boolean>; basics?: { firstName?: ValidationFunction<string>; emailAddress?: ValidationFunction<string>; }; favoriteThings?: { animals?: ValidationFunction<string>; month?: ValidationFunction<string>; number?: ValidationFunction<string>; }; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type HappyPathJSONUpdateOverridesProps = { HappyPathJSONUpdateGrid?: PrimitiveOverrideProps<GridProps>; basics?: PrimitiveOverrideProps<HeadingProps>; \\"basics.firstName\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"basics.emailAddress\\"?: PrimitiveOverrideProps<TextFieldProps>; favoriteThings?: PrimitiveOverrideProps<HeadingProps>; \\"favoriteThings.animals\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"favoriteThings.month\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"favoriteThings.number\\"?: PrimitiveOverrideProps<TextFieldProps>; activeSectionalElement?: PrimitiveOverrideProps<DividerProps>; active?: PrimitiveOverrideProps<SwitchFieldProps>; enabled?: PrimitiveOverrideProps<SwitchFieldProps>; } & EscapeHatchProps; export declare type HappyPathJSONUpdateProps = React.PropsWithChildren<{ overrides?: HappyPathJSONUpdateOverridesProps | undefined | null; } & { initialData?: HappyPathJSONUpdateInputValues; onSubmit: (fields: HappyPathJSONUpdateInputValues) => void; onChange?: (fields: HappyPathJSONUpdateInputValues) => HappyPathJSONUpdateInputValues; onValidate?: HappyPathJSONUpdateValidationValues; } & React.CSSProperties>; export default function HappyPathJSONUpdate(props: HappyPathJSONUpdateProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render nested json fields for create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Heading, Icon, ScrollView, SwitchField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function NestedJson(props) { const { onSubmit, onValidate, onChange, overrides, ...rest } = props; const { tokens } = useTheme(); const initialValues = { \\"first-Name\\": \\"\\", lastName: \\"\\", bio: {}, Nicknames1: [], \\"nick-names2\\": [], \\"first Name\\": \\"\\", options: {}, }; const [firstName, setFirstName] = React.useState(initialValues[\\"first-Name\\"]); const [lastName, setLastName] = React.useState(initialValues.lastName); const [bio, setBio] = React.useState(initialValues.bio); const [Nicknames1, setNicknames1] = React.useState(initialValues.Nicknames1); const [nickNames, setNickNames] = React.useState( initialValues[\\"nick-names2\\"] ); const [firstName1, setFirstName1] = React.useState( initialValues[\\"first Name\\"] ); const [options, setOptions] = React.useState(initialValues.options); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setFirstName(initialValues[\\"first-Name\\"]); setLastName(initialValues.lastName); setBio(initialValues.bio); setNicknames1(initialValues.Nicknames1); setCurrentNicknames1Value(\\"\\"); setNickNames(initialValues[\\"nick-names2\\"]); setCurrentNickNamesValue(\\"\\"); setFirstName1(initialValues[\\"first Name\\"]); setOptions(initialValues.options); setErrors({}); }; const [currentBioFavoritetreesValue, setCurrentBioFavoritetreesValue] = React.useState(\\"\\"); const bioFavoritetreesRef = React.createRef(); const [currentNicknames1Value, setCurrentNicknames1Value] = React.useState(\\"\\"); const Nicknames1Ref = React.createRef(); const [currentNickNamesValue, setCurrentNickNamesValue] = React.useState(\\"\\"); const nickNamesRef = React.createRef(); const validations = { \\"first-Name\\": [], lastName: [], \\"bio.favorite Quote\\": [], \\"bio.favorite-Animal\\": [], \\"bio.favorite-trees\\": [], Nicknames1: [], \\"nick-names2\\": [], \\"first Name\\": [], \\"options.enabled\\": [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap={tokens.space.large.value} columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { \\"first-Name\\": firstName, lastName, bio, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"NestedJson\\")} {...rest} > <TextField label=\\"firstName\\" value={firstName} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { \\"first-Name\\": value, lastName, bio, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); value = result?.[\\"first-Name\\"] ?? value; } if (errors[\\"first-Name\\"]?.hasError) { runValidationTasks(\\"first-Name\\", value); } setFirstName(value); }} onBlur={() => runValidationTasks(\\"first-Name\\", firstName)} errorMessage={errors[\\"first-Name\\"]?.errorMessage} hasError={errors[\\"first-Name\\"]?.hasError} {...getOverrideProps(overrides, \\"first-Name\\")} ></TextField> <TextField label=\\"lastName\\" value={lastName} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName: value, bio, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); value = result?.lastName ?? value; } if (errors.lastName?.hasError) { runValidationTasks(\\"lastName\\", value); } setLastName(value); }} onBlur={() => runValidationTasks(\\"lastName\\", lastName)} errorMessage={errors.lastName?.errorMessage} hasError={errors.lastName?.hasError} {...getOverrideProps(overrides, \\"lastName\\")} ></TextField> <Heading level={3} children=\\"bio\\" {...getOverrideProps(overrides, \\"bio\\")} ></Heading> <TextField label=\\"favoriteQuote\\" value={bio[\\"favorite Quote\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio: { ...bio, [\\"favorite Quote\\"]: value }, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); value = result?.bio?.[\\"favorite Quote\\"] ?? value; } if (errors[\\"bio.favorite Quote\\"]?.hasError) { runValidationTasks(\\"bio.favorite Quote\\", value); } setBio({ ...bio, [\\"favorite Quote\\"]: value }); }} onBlur={() => runValidationTasks(\\"bio.favorite Quote\\", bio[\\"favorite Quote\\"]) } errorMessage={errors[\\"bio.favorite Quote\\"]?.errorMessage} hasError={errors[\\"bio.favorite Quote\\"]?.hasError} {...getOverrideProps(overrides, \\"bio.favorite Quote\\")} ></TextField> <TextField label=\\"favoriteAnimal\\" value={bio[\\"favorite-Animal\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio: { ...bio, [\\"favorite-Animal\\"]: value }, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); value = result?.bio?.[\\"favorite-Animal\\"] ?? value; } if (errors[\\"bio.favorite-Animal\\"]?.hasError) { runValidationTasks(\\"bio.favorite-Animal\\", value); } setBio({ ...bio, [\\"favorite-Animal\\"]: value }); }} onBlur={() => runValidationTasks(\\"bio.favorite-Animal\\", bio[\\"favorite-Animal\\"]) } errorMessage={errors[\\"bio.favorite-Animal\\"]?.errorMessage} hasError={errors[\\"bio.favorite-Animal\\"]?.hasError} {...getOverrideProps(overrides, \\"bio.favorite-Animal\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio: { ...bio, [\\"favorite-trees\\"]: values }, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); values = result?.bio?.[\\"favorite-trees\\"] ?? values; } setBio({ ...bio, [\\"favorite-trees\\"]: values }); setCurrentBioFavoritetreesValue(\\"\\"); }} currentFieldValue={currentBioFavoritetreesValue} label={\\"favorite trees\\"} items={bio.favorite - trees ?? []} hasError={errors?.[\\"bio.favorite-trees\\"]?.hasError} errorMessage={errors?.[\\"bio.favorite-trees\\"]?.errorMessage} setFieldValue={setCurrentBioFavoritetreesValue} inputFieldRef={bioFavoritetreesRef} defaultFieldValue={\\"\\"} > <TextField label=\\"favorite trees\\" value={currentBioFavoritetreesValue} onChange={(e) => { let { value } = e.target; if (errors[\\"bio.favorite-trees\\"]?.hasError) { runValidationTasks(\\"bio.favorite-trees\\", value); } setCurrentBioFavoritetreesValue(value); }} onBlur={() => runValidationTasks( \\"bio.favorite-trees\\", currentBioFavoritetreesValue ) } errorMessage={errors[\\"bio.favorite-trees\\"]?.errorMessage} hasError={errors[\\"bio.favorite-trees\\"]?.hasError} ref={bioFavoritetreesRef} labelHidden={true} {...getOverrideProps(overrides, \\"bio.favorite-trees\\")} ></TextField> </ArrayField> <Heading level={3} children=\\"Options\\" {...getOverrideProps(overrides, \\"options\\")} ></Heading> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio, Nicknames1: values, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); values = result?.Nicknames1 ?? values; } setNicknames1(values); setCurrentNicknames1Value(\\"\\"); }} currentFieldValue={currentNicknames1Value} label={\\"Nick Names1\\"} items={Nicknames1} hasError={errors?.Nicknames1?.hasError} errorMessage={errors?.Nicknames1?.errorMessage} setFieldValue={setCurrentNicknames1Value} inputFieldRef={Nicknames1Ref} defaultFieldValue={\\"\\"} > <TextField label=\\"Nick Names1\\" value={currentNicknames1Value} onChange={(e) => { let { value } = e.target; if (errors.Nicknames1?.hasError) { runValidationTasks(\\"Nicknames1\\", value); } setCurrentNicknames1Value(value); }} onBlur={() => runValidationTasks(\\"Nicknames1\\", currentNicknames1Value) } errorMessage={errors.Nicknames1?.errorMessage} hasError={errors.Nicknames1?.hasError} ref={Nicknames1Ref} labelHidden={true} {...getOverrideProps(overrides, \\"Nicknames1\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio, Nicknames1, \\"nick-names2\\": values, \\"first Name\\": firstName1, options, }; const result = onChange(modelFields); values = result?.[\\"nick-names2\\"] ?? values; } setNickNames(values); setCurrentNickNamesValue(\\"\\"); }} currentFieldValue={currentNickNamesValue} label={\\"nick-Names2\\"} items={nickNames} hasError={errors?.[\\"nick-names2\\"]?.hasError} errorMessage={errors?.[\\"nick-names2\\"]?.errorMessage} setFieldValue={setCurrentNickNamesValue} inputFieldRef={nickNamesRef} defaultFieldValue={\\"\\"} > <TextField label=\\"nick-Names2\\" value={currentNickNamesValue} onChange={(e) => { let { value } = e.target; if (errors[\\"nick-names2\\"]?.hasError) { runValidationTasks(\\"nick-names2\\", value); } setCurrentNickNamesValue(value); }} onBlur={() => runValidationTasks(\\"nick-names2\\", currentNickNamesValue) } errorMessage={errors[\\"nick-names2\\"]?.errorMessage} hasError={errors[\\"nick-names2\\"]?.hasError} ref={nickNamesRef} labelHidden={true} {...getOverrideProps(overrides, \\"nick-names2\\")} ></TextField> </ArrayField> <TextField label=\\"first Name1\\" value={firstName1} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": value, options, }; const result = onChange(modelFields); value = result?.[\\"first Name\\"] ?? value; } if (errors[\\"first Name\\"]?.hasError) { runValidationTasks(\\"first Name\\", value); } setFirstName1(value); }} onBlur={() => runValidationTasks(\\"first Name\\", firstName1)} errorMessage={errors[\\"first Name\\"]?.errorMessage} hasError={errors[\\"first Name\\"]?.hasError} {...getOverrideProps(overrides, \\"first Name\\")} ></TextField> <SwitchField label=\\"Enabled\\" defaultChecked={false} isChecked={options.enabled} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { \\"first-Name\\": firstName, lastName, bio, Nicknames1, \\"nick-names2\\": nickNames, \\"first Name\\": firstName1, options: { ...options, enabled: value }, }; const result = onChange(modelFields); value = result?.options?.enabled ?? value; } if (errors[\\"options.enabled\\"]?.hasError) { runValidationTasks(\\"options.enabled\\", value); } setOptions({ ...options, enabled: value }); }} onBlur={() => runValidationTasks(\\"options.enabled\\", options[\\"enabled\\"])} errorMessage={errors[\\"options.enabled\\"]?.errorMessage} hasError={errors[\\"options.enabled\\"]?.hasError} {...getOverrideProps(overrides, \\"options.enabled\\")} ></SwitchField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render nested json fields for create form 2`] = ` "import * as React from \\"react\\"; import { GridProps, HeadingProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type NestedJsonInputValues = { \\"first-Name\\"?: string; lastName?: string; Nicknames1?: string[]; \\"nick-names2\\"?: string[]; \\"first Name\\"?: string; bio?: { \\"favorite Quote\\"?: string; \\"favorite-Animal\\"?: string; \\"favorite-trees\\"?: string[]; }; options?: { enabled?: boolean; }; }; export declare type NestedJsonValidationValues = { \\"first-Name\\"?: ValidationFunction<string>; lastName?: ValidationFunction<string>; Nicknames1?: ValidationFunction<string>; \\"nick-names2\\"?: ValidationFunction<string>; \\"first Name\\"?: ValidationFunction<string>; bio?: { \\"favorite Quote\\"?: ValidationFunction<string>; \\"favorite-Animal\\"?: ValidationFunction<string>; \\"favorite-trees\\"?: ValidationFunction<string>; }; options?: { enabled?: ValidationFunction<boolean>; }; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type NestedJsonOverridesProps = { NestedJsonGrid?: PrimitiveOverrideProps<GridProps>; \\"first-Name\\"?: PrimitiveOverrideProps<TextFieldProps>; lastName?: PrimitiveOverrideProps<TextFieldProps>; bio?: PrimitiveOverrideProps<HeadingProps>; \\"bio.favorite Quote\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"bio.favorite-Animal\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"bio.favorite-trees\\"?: PrimitiveOverrideProps<TextFieldProps>; options?: PrimitiveOverrideProps<HeadingProps>; Nicknames1?: PrimitiveOverrideProps<TextFieldProps>; \\"nick-names2\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"first Name\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"options.enabled\\"?: PrimitiveOverrideProps<SwitchFieldProps>; } & EscapeHatchProps; export declare type NestedJsonProps = React.PropsWithChildren<{ overrides?: NestedJsonOverridesProps | undefined | null; } & { onSubmit: (fields: NestedJsonInputValues) => void; onChange?: (fields: NestedJsonInputValues) => NestedJsonInputValues; onValidate?: NestedJsonValidationValues; } & React.CSSProperties>; export default function NestedJson(props: NestedJsonProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render nested json fields for update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Heading, Icon, ScrollView, SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function NestedJson(props) { const { initialData, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { firstName: \\"\\", \\"last-Name\\": \\"\\", lastName: [], bio: {}, }; const [firstName, setFirstName] = React.useState(initialValues.firstName); const [lastName, setLastName] = React.useState(initialValues[\\"last-Name\\"]); const [lastName1, setLastName1] = React.useState(initialValues.lastName); const [bio, setBio] = React.useState(initialValues.bio); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = initialData ? { ...initialValues, ...initialData } : initialValues; setFirstName(cleanValues.firstName); setLastName(cleanValues[\\"last-Name\\"]); setLastName1(cleanValues.lastName ?? []); setCurrentLastName1Value(\\"\\"); setBio(cleanValues.bio); setErrors({}); }; React.useEffect(resetStateValues, [initialData]); React.useEffect(() => { if (initialData) { setFirstName(initialData.firstName); setLastName(initialData[\\"last-Name\\"]); setLastName1(initialData.lastName); setBio(initialData.bio); } }, []); const [currentLastName1Value, setCurrentLastName1Value] = React.useState(\\"\\"); const lastName1Ref = React.createRef(); const validations = { firstName: [], \\"last-Name\\": [], lastName: [], \\"bio.favoriteQuote\\": [], \\"bio.favoriteAnimal\\": [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { firstName, \\"last-Name\\": lastName, lastName: lastName1, bio, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"NestedJson\\")} {...rest} > <TextField label=\\"firstName\\" value={firstName} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { firstName: value, \\"last-Name\\": lastName, lastName: lastName1, bio, }; const result = onChange(modelFields); value = result?.firstName ?? value; } if (errors.firstName?.hasError) { runValidationTasks(\\"firstName\\", value); } setFirstName(value); }} onBlur={() => runValidationTasks(\\"firstName\\", firstName)} errorMessage={errors.firstName?.errorMessage} hasError={errors.firstName?.hasError} {...getOverrideProps(overrides, \\"firstName\\")} ></TextField> <SelectField label=\\"last-Name\\" placeholder=\\"Please select an option\\" value={lastName} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { firstName, \\"last-Name\\": value, lastName: lastName1, bio, }; const result = onChange(modelFields); value = result?.[\\"last-Name\\"] ?? value; } if (errors[\\"last-Name\\"]?.hasError) { runValidationTasks(\\"last-Name\\", value); } setLastName(value); }} onBlur={() => runValidationTasks(\\"last-Name\\", lastName)} errorMessage={errors[\\"last-Name\\"]?.errorMessage} hasError={errors[\\"last-Name\\"]?.hasError} {...getOverrideProps(overrides, \\"last-Name\\")} ></SelectField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { firstName, \\"last-Name\\": lastName, lastName: values, bio, }; const result = onChange(modelFields); values = result?.lastName ?? values; } setLastName1(values); setCurrentLastName1Value(\\"\\"); }} currentFieldValue={currentLastName1Value} label={\\"lastName\\"} items={lastName1} hasError={errors?.lastName?.hasError} errorMessage={errors?.lastName?.errorMessage} setFieldValue={setCurrentLastName1Value} inputFieldRef={lastName1Ref} defaultFieldValue={\\"\\"} > <TextField label=\\"lastName\\" value={currentLastName1Value} onChange={(e) => { let { value } = e.target; if (errors.lastName?.hasError) { runValidationTasks(\\"lastName\\", value); } setCurrentLastName1Value(value); }} onBlur={() => runValidationTasks(\\"lastName\\", currentLastName1Value)} errorMessage={errors.lastName?.errorMessage} hasError={errors.lastName?.hasError} ref={lastName1Ref} labelHidden={true} {...getOverrideProps(overrides, \\"lastName\\")} ></TextField> </ArrayField> <Heading level={3} children=\\"bio\\" {...getOverrideProps(overrides, \\"bio\\")} ></Heading> <TextField label=\\"favoriteQuote\\" value={bio[\\"favoriteQuote\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { firstName, \\"last-Name\\": lastName, lastName: lastName1, bio: { ...bio, favoriteQuote: value }, }; const result = onChange(modelFields); value = result?.bio?.favoriteQuote ?? value; } if (errors[\\"bio.favoriteQuote\\"]?.hasError) { runValidationTasks(\\"bio.favoriteQuote\\", value); } setBio({ ...bio, favoriteQuote: value }); }} onBlur={() => runValidationTasks(\\"bio.favoriteQuote\\", bio[\\"favoriteQuote\\"]) } errorMessage={errors[\\"bio.favoriteQuote\\"]?.errorMessage} hasError={errors[\\"bio.favoriteQuote\\"]?.hasError} {...getOverrideProps(overrides, \\"bio.favoriteQuote\\")} ></TextField> <TextField label=\\"favoriteAnimal\\" value={bio[\\"favoriteAnimal\\"] ?? \\"\\"} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { firstName, \\"last-Name\\": lastName, lastName: lastName1, bio: { ...bio, favoriteAnimal: value }, }; const result = onChange(modelFields); value = result?.bio?.favoriteAnimal ?? value; } if (errors[\\"bio.favoriteAnimal\\"]?.hasError) { runValidationTasks(\\"bio.favoriteAnimal\\", value); } setBio({ ...bio, favoriteAnimal: value }); }} onBlur={() => runValidationTasks(\\"bio.favoriteAnimal\\", bio[\\"favoriteAnimal\\"]) } errorMessage={errors[\\"bio.favoriteAnimal\\"]?.errorMessage} hasError={errors[\\"bio.favoriteAnimal\\"]?.hasError} {...getOverrideProps(overrides, \\"bio.favoriteAnimal\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render nested json fields for update form 2`] = ` "import * as React from \\"react\\"; import { GridProps, HeadingProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type NestedJsonInputValues = { firstName?: string; \\"last-Name\\"?: string; lastName?: string[]; bio?: { favoriteQuote?: string; favoriteAnimal?: string; }; }; export declare type NestedJsonValidationValues = { firstName?: ValidationFunction<string>; \\"last-Name\\"?: ValidationFunction<string>; lastName?: ValidationFunction<string>; bio?: { favoriteQuote?: ValidationFunction<string>; favoriteAnimal?: ValidationFunction<string>; }; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type NestedJsonOverridesProps = { NestedJsonGrid?: PrimitiveOverrideProps<GridProps>; firstName?: PrimitiveOverrideProps<TextFieldProps>; \\"last-Name\\"?: PrimitiveOverrideProps<SelectFieldProps>; lastName?: PrimitiveOverrideProps<TextFieldProps>; bio?: PrimitiveOverrideProps<HeadingProps>; \\"bio.favoriteQuote\\"?: PrimitiveOverrideProps<TextFieldProps>; \\"bio.favoriteAnimal\\"?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type NestedJsonProps = React.PropsWithChildren<{ overrides?: NestedJsonOverridesProps | undefined | null; } & { initialData?: NestedJsonInputValues; onSubmit: (fields: NestedJsonInputValues) => void; onChange?: (fields: NestedJsonInputValues) => NestedJsonInputValues; onValidate?: NestedJsonValidationValues; } & React.CSSProperties>; export default function NestedJson(props: NestedJsonProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render scalar relationship fields if overrides 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeDog, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet, CompositeDogCompositeVet, CompositeBowl, } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateCompositeDogForm(props) { const { id: idProp, compositeDog: compositeDogModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", description: \\"\\", CompositeOwner: undefined, CompositeToys: [], CompositeVets: [], compositeDogCompositeBowlShape: undefined, }; const [name, setName] = React.useState(initialValues.name); const [description, setDescription] = React.useState( initialValues.description ); const [CompositeOwner, setCompositeOwner] = React.useState( initialValues.CompositeOwner ); const [CompositeToys, setCompositeToys] = React.useState( initialValues.CompositeToys ); const [CompositeVets, setCompositeVets] = React.useState( initialValues.CompositeVets ); const [compositeDogCompositeBowlShape, setCompositeDogCompositeBowlShape] = React.useState(initialValues.compositeDogCompositeBowlShape); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = compositeDogRecord ? { ...initialValues, ...compositeDogRecord, CompositeOwner, CompositeToys: linkedCompositeToys, CompositeVets: linkedCompositeVets, compositeDogCompositeBowlShape, } : initialValues; setName(cleanValues.name); setDescription(cleanValues.description); setCompositeOwner(cleanValues.CompositeOwner); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); setCompositeToys(cleanValues.CompositeToys ?? []); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); setCompositeVets(cleanValues.CompositeVets ?? []); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); setCompositeDogCompositeBowlShape( cleanValues.compositeDogCompositeBowlShape ); setCurrentCompositeDogCompositeBowlShapeValue(undefined); setCurrentCompositeDogCompositeBowlShapeDisplayValue(\\"\\"); setErrors({}); }; const [compositeDogRecord, setCompositeDogRecord] = React.useState( compositeDogModelProp ); const [linkedCompositeToys, setLinkedCompositeToys] = React.useState([]); const canUnlinkCompositeToys = true; const [linkedCompositeVets, setLinkedCompositeVets] = React.useState([]); const canUnlinkCompositeVets = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(CompositeDog, idProp) : compositeDogModelProp; setCompositeDogRecord(record); const CompositeOwnerRecord = record ? await record.CompositeOwner : undefined; setCompositeOwner(CompositeOwnerRecord); const linkedCompositeToys = record ? await record.CompositeToys.toArray() : []; setLinkedCompositeToys(linkedCompositeToys); const linkedCompositeVets = record ? await Promise.all( ( await record.CompositeVets.toArray() ).map((r) => { return r.compositeVet; }) ) : []; setLinkedCompositeVets(linkedCompositeVets); const compositeDogCompositeBowlShapeRecord = record ? await record.compositeDogCompositeBowlShape : undefined; setCompositeDogCompositeBowlShape(compositeDogCompositeBowlShapeRecord); }; queryData(); }, [idProp, compositeDogModelProp]); React.useEffect(resetStateValues, [ compositeDogRecord, CompositeOwner, linkedCompositeToys, linkedCompositeVets, compositeDogCompositeBowlShape, ]); const [ currentCompositeOwnerDisplayValue, setCurrentCompositeOwnerDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeOwnerValue, setCurrentCompositeOwnerValue] = React.useState(undefined); const CompositeOwnerRef = React.createRef(); const [ currentCompositeToysDisplayValue, setCurrentCompositeToysDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeToysValue, setCurrentCompositeToysValue] = React.useState(undefined); const CompositeToysRef = React.createRef(); const [ currentCompositeVetsDisplayValue, setCurrentCompositeVetsDisplayValue, ] = React.useState(\\"\\"); const [currentCompositeVetsValue, setCurrentCompositeVetsValue] = React.useState(undefined); const CompositeVetsRef = React.createRef(); const [ currentCompositeDogCompositeBowlShapeDisplayValue, setCurrentCompositeDogCompositeBowlShapeDisplayValue, ] = React.useState(\\"\\"); const [ currentCompositeDogCompositeBowlShapeValue, setCurrentCompositeDogCompositeBowlShapeValue, ] = React.useState(undefined); const compositeDogCompositeBowlShapeRef = React.createRef(); const getIDValue = { CompositeOwner: (r) => JSON.stringify({ lastName: r?.lastName, firstName: r?.firstName }), CompositeToys: (r) => JSON.stringify({ kind: r?.kind, color: r?.color }), CompositeVets: (r) => JSON.stringify({ specialty: r?.specialty, city: r?.city }), }; const CompositeOwnerIdSet = new Set( Array.isArray(CompositeOwner) ? CompositeOwner.map((r) => getIDValue.CompositeOwner?.(r)) : getIDValue.CompositeOwner?.(CompositeOwner) ); const CompositeToysIdSet = new Set( Array.isArray(CompositeToys) ? CompositeToys.map((r) => getIDValue.CompositeToys?.(r)) : getIDValue.CompositeToys?.(CompositeToys) ); const CompositeVetsIdSet = new Set( Array.isArray(CompositeVets) ? CompositeVets.map((r) => getIDValue.CompositeVets?.(r)) : getIDValue.CompositeVets?.(CompositeVets) ); const compositeOwnerRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeOwner0, }).items; const compositeToyRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeToy, }).items; const compositeVetRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeVet, }).items; const compositeBowlRecords = useDataStoreBinding({ type: \\"collection\\", model: CompositeBowl, }).items; const getDisplayValue = { CompositeOwner: (r) => \`\${r?.lastName}\${\\"-\\"}\${r?.firstName}\`, CompositeToys: (r) => \`\${r?.kind}\${\\"-\\"}\${r?.color}\`, CompositeVets: (r) => \`\${r?.specialty}\${\\"-\\"}\${r?.city}\`, compositeDogCompositeBowlShape: (r) => r?.shape, }; const validations = { name: [{ type: \\"Required\\" }], description: [{ type: \\"Required\\" }], CompositeOwner: [], CompositeToys: [], CompositeVets: [], compositeDogCompositeBowlShape: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, description, CompositeOwner, CompositeToys, CompositeVets, compositeDogCompositeBowlShape, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const compositeOwnerToUnlink = await compositeDogRecord.CompositeOwner; if (compositeOwnerToUnlink) { promises.push( DataStore.save( CompositeOwner0.copyOf(compositeOwnerToUnlink, (updated) => { updated.CompositeDog = undefined; updated.compositeOwnerCompositeDogName = undefined; updated.compositeOwnerCompositeDogDescription = undefined; }) ) ); } const compositeOwnerToLink = modelFields.CompositeOwner; if (compositeOwnerToLink) { promises.push( DataStore.save( CompositeOwner0.copyOf(compositeOwnerToLink, (updated) => { updated.CompositeDog = compositeDogRecord; }) ) ); const compositeDogToUnlink = await compositeOwnerToLink.CompositeDog; if (compositeDogToUnlink) { promises.push( DataStore.save( CompositeDog.copyOf(compositeDogToUnlink, (updated) => { updated.CompositeOwner = undefined; updated.compositeDogCompositeOwnerLastName = undefined; updated.compositeDogCompositeOwnerFirstName = undefined; }) ) ); } } const compositeToysToLink = []; const compositeToysToUnLink = []; const compositeToysSet = new Set(); const linkedCompositeToysSet = new Set(); CompositeToys.forEach((r) => compositeToysSet.add(getIDValue.CompositeToys?.(r)) ); linkedCompositeToys.forEach((r) => linkedCompositeToysSet.add(getIDValue.CompositeToys?.(r)) ); linkedCompositeToys.forEach((r) => { if (!compositeToysSet.has(getIDValue.CompositeToys?.(r))) { compositeToysToUnLink.push(r); } }); CompositeToys.forEach((r) => { if (!linkedCompositeToysSet.has(getIDValue.CompositeToys?.(r))) { compositeToysToLink.push(r); } }); compositeToysToUnLink.forEach((original) => { if (!canUnlinkCompositeToys) { throw Error( \`CompositeToy \${original.kind} cannot be unlinked from CompositeDog because compositeDogCompositeToysName is a required field.\` ); } promises.push( DataStore.save( CompositeToy.copyOf(original, (updated) => { updated.compositeDogCompositeToysName = null; updated.compositeDogCompositeToysDescription = null; }) ) ); }); compositeToysToLink.forEach((original) => { promises.push( DataStore.save( CompositeToy.copyOf(original, (updated) => { updated.compositeDogCompositeToysName = compositeDogRecord.name; updated.compositeDogCompositeToysDescription = compositeDogRecord.description; }) ) ); }); const compositeVetsToLinkMap = new Map(); const compositeVetsToUnLinkMap = new Map(); const compositeVetsMap = new Map(); const linkedCompositeVetsMap = new Map(); CompositeVets.forEach((r) => { const count = compositeVetsMap.get(getIDValue.CompositeVets?.(r)); const newCount = count ? count + 1 : 1; compositeVetsMap.set(getIDValue.CompositeVets?.(r), newCount); }); linkedCompositeVets.forEach((r) => { const count = linkedCompositeVetsMap.get( getIDValue.CompositeVets?.(r) ); const newCount = count ? count + 1 : 1; linkedCompositeVetsMap.set(getIDValue.CompositeVets?.(r), newCount); }); linkedCompositeVetsMap.forEach((count, id) => { const newCount = compositeVetsMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { compositeVetsToUnLinkMap.set(id, diffCount); } } else { compositeVetsToUnLinkMap.set(id, count); } }); compositeVetsMap.forEach((count, id) => { const originalCount = linkedCompositeVetsMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { compositeVetsToLinkMap.set(id, diffCount); } } else { compositeVetsToLinkMap.set(id, count); } }); compositeVetsToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const compositeDogCompositeVetRecords = await DataStore.query( CompositeDogCompositeVet, (r) => r.and((r) => { return [ r.compositeVetSpecialty.eq(recordKeys.specialty), r.compositeVetcity.eq(recordKeys.city), r.compositeDogName.eq(compositeDogRecord.name), r.compositeDogdescription.eq( compositeDogRecord.description ), ]; }) ); for (let i = 0; i < count; i++) { promises.push( DataStore.delete(compositeDogCompositeVetRecords[i]) ); } }); compositeVetsToLinkMap.forEach((count, id) => { const compositeVetToLink = compositeVetRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( DataStore.save( new CompositeDogCompositeVet({ compositeDog: compositeDogRecord, compositeVet: compositeVetToLink, }) ) ); } }); const modelFieldsToSave = { name: modelFields.name, description: modelFields.description, CompositeOwner: modelFields.CompositeOwner, compositeDogCompositeBowlShape: modelFields.compositeDogCompositeBowlShape, }; promises.push( DataStore.save( CompositeDog.copyOf(compositeDogRecord, (updated) => { Object.assign(updated, modelFieldsToSave); if (!modelFieldsToSave.CompositeOwner) { updated.compositeDogCompositeOwnerLastName = undefined; updated.compositeDogCompositeOwnerFirstName = undefined; } }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateCompositeDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={true} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, description, CompositeOwner, CompositeToys, CompositeVets, compositeDogCompositeBowlShape, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <TextField label=\\"Description\\" isRequired={true} isReadOnly={true} value={description} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, description: value, CompositeOwner, CompositeToys, CompositeVets, compositeDogCompositeBowlShape, }; const result = onChange(modelFields); value = result?.description ?? value; } if (errors.description?.hasError) { runValidationTasks(\\"description\\", value); } setDescription(value); }} onBlur={() => runValidationTasks(\\"description\\", description)} errorMessage={errors.description?.errorMessage} hasError={errors.description?.hasError} {...getOverrideProps(overrides, \\"description\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeOwner: value, CompositeToys, CompositeVets, compositeDogCompositeBowlShape, }; const result = onChange(modelFields); value = result?.CompositeOwner ?? value; } setCompositeOwner(value); setCurrentCompositeOwnerValue(undefined); setCurrentCompositeOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeOwnerValue} label={\\"Composite owner\\"} items={CompositeOwner ? [CompositeOwner] : []} hasError={errors?.CompositeOwner?.hasError} errorMessage={errors?.CompositeOwner?.errorMessage} getBadgeText={getDisplayValue.CompositeOwner} setFieldValue={(model) => { setCurrentCompositeOwnerDisplayValue( model ? getDisplayValue.CompositeOwner(model) : \\"\\" ); setCurrentCompositeOwnerValue(model); }} inputFieldRef={CompositeOwnerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite owner\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeOwner\\" value={currentCompositeOwnerDisplayValue} options={compositeOwnerRecords .filter( (r) => !CompositeOwnerIdSet.has(getIDValue.CompositeOwner?.(r)) ) .map((r) => ({ id: getIDValue.CompositeOwner?.(r), label: getDisplayValue.CompositeOwner?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeOwnerValue( compositeOwnerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeOwnerDisplayValue(label); runValidationTasks(\\"CompositeOwner\\", label); }} onClear={() => { setCurrentCompositeOwnerDisplayValue(\\"\\"); }} defaultValue={CompositeOwner} onChange={(e) => { let { value } = e.target; if (errors.CompositeOwner?.hasError) { runValidationTasks(\\"CompositeOwner\\", value); } setCurrentCompositeOwnerDisplayValue(value); setCurrentCompositeOwnerValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeOwner\\", currentCompositeOwnerDisplayValue ) } errorMessage={errors.CompositeOwner?.errorMessage} hasError={errors.CompositeOwner?.hasError} ref={CompositeOwnerRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeOwner\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeOwner, CompositeToys: values, CompositeVets, compositeDogCompositeBowlShape, }; const result = onChange(modelFields); values = result?.CompositeToys ?? values; } setCompositeToys(values); setCurrentCompositeToysValue(undefined); setCurrentCompositeToysDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeToysValue} label={\\"Composite toys\\"} items={CompositeToys} hasError={errors?.CompositeToys?.hasError} errorMessage={errors?.CompositeToys?.errorMessage} getBadgeText={getDisplayValue.CompositeToys} setFieldValue={(model) => { setCurrentCompositeToysDisplayValue( model ? getDisplayValue.CompositeToys(model) : \\"\\" ); setCurrentCompositeToysValue(model); }} inputFieldRef={CompositeToysRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite toys\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeToy\\" value={currentCompositeToysDisplayValue} options={compositeToyRecords .filter( (r) => !CompositeToysIdSet.has(getIDValue.CompositeToys?.(r)) ) .map((r) => ({ id: getIDValue.CompositeToys?.(r), label: getDisplayValue.CompositeToys?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeToysValue( compositeToyRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeToysDisplayValue(label); runValidationTasks(\\"CompositeToys\\", label); }} onClear={() => { setCurrentCompositeToysDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeToys?.hasError) { runValidationTasks(\\"CompositeToys\\", value); } setCurrentCompositeToysDisplayValue(value); setCurrentCompositeToysValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeToys\\", currentCompositeToysDisplayValue ) } errorMessage={errors.CompositeToys?.errorMessage} hasError={errors.CompositeToys?.hasError} ref={CompositeToysRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeToys\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, description, CompositeOwner, CompositeToys, CompositeVets: values, compositeDogCompositeBowlShape, }; const result = onChange(modelFields); values = result?.CompositeVets ?? values; } setCompositeVets(values); setCurrentCompositeVetsValue(undefined); setCurrentCompositeVetsDisplayValue(\\"\\"); }} currentFieldValue={currentCompositeVetsValue} label={\\"Composite vets\\"} items={CompositeVets} hasError={errors?.CompositeVets?.hasError} errorMessage={errors?.CompositeVets?.errorMessage} getBadgeText={getDisplayValue.CompositeVets} setFieldValue={(model) => { setCurrentCompositeVetsDisplayValue( model ? getDisplayValue.CompositeVets(model) : \\"\\" ); setCurrentCompositeVetsValue(model); }} inputFieldRef={CompositeVetsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite vets\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeVet\\" value={currentCompositeVetsDisplayValue} options={compositeVetRecords .filter( (r) => !CompositeVetsIdSet.has(getIDValue.CompositeVets?.(r)) ) .map((r) => ({ id: getIDValue.CompositeVets?.(r), label: getDisplayValue.CompositeVets?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeVetsValue( compositeVetRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCompositeVetsDisplayValue(label); runValidationTasks(\\"CompositeVets\\", label); }} onClear={() => { setCurrentCompositeVetsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.CompositeVets?.hasError) { runValidationTasks(\\"CompositeVets\\", value); } setCurrentCompositeVetsDisplayValue(value); setCurrentCompositeVetsValue(undefined); }} onBlur={() => runValidationTasks( \\"CompositeVets\\", currentCompositeVetsDisplayValue ) } errorMessage={errors.CompositeVets?.errorMessage} hasError={errors.CompositeVets?.hasError} ref={CompositeVetsRef} labelHidden={true} {...getOverrideProps(overrides, \\"CompositeVets\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, description, CompositeOwner, CompositeToys, CompositeVets, compositeDogCompositeBowlShape: value, }; const result = onChange(modelFields); value = result?.compositeDogCompositeBowlShape ?? value; } setCompositeDogCompositeBowlShape(value); setCurrentCompositeDogCompositeBowlShapeValue(undefined); }} currentFieldValue={currentCompositeDogCompositeBowlShapeValue} label={\\"Composite dog composite bowl shape\\"} items={ compositeDogCompositeBowlShape ? [compositeDogCompositeBowlShape] : [] } hasError={errors?.compositeDogCompositeBowlShape?.hasError} errorMessage={errors?.compositeDogCompositeBowlShape?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.compositeDogCompositeBowlShape( compositeBowlRecords.find((r) => r.shape === value) ) : \\"\\" } setFieldValue={(value) => { setCurrentCompositeDogCompositeBowlShapeDisplayValue( value ? getDisplayValue.compositeDogCompositeBowlShape( compositeBowlRecords.find((r) => r.shape === value) ) : \\"\\" ); setCurrentCompositeDogCompositeBowlShapeValue(value); }} inputFieldRef={compositeDogCompositeBowlShapeRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Composite dog composite bowl shape\\" isRequired={false} isReadOnly={false} placeholder=\\"Search CompositeBowl\\" value={currentCompositeDogCompositeBowlShapeDisplayValue} options={compositeBowlRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.shape === r?.shape) === i ) .map((r) => ({ id: r?.shape, label: getDisplayValue.compositeDogCompositeBowlShape?.(r), }))} onSelect={({ id, label }) => { setCurrentCompositeDogCompositeBowlShapeValue(id); setCurrentCompositeDogCompositeBowlShapeDisplayValue(label); runValidationTasks(\\"compositeDogCompositeBowlShape\\", label); }} onClear={() => { setCurrentCompositeDogCompositeBowlShapeDisplayValue(\\"\\"); }} defaultValue={compositeDogCompositeBowlShape} onChange={(e) => { let { value } = e.target; if (errors.compositeDogCompositeBowlShape?.hasError) { runValidationTasks(\\"compositeDogCompositeBowlShape\\", value); } setCurrentCompositeDogCompositeBowlShapeDisplayValue(value); setCurrentCompositeDogCompositeBowlShapeValue(undefined); }} onBlur={() => runValidationTasks( \\"compositeDogCompositeBowlShape\\", currentCompositeDogCompositeBowlShapeValue ) } errorMessage={errors.compositeDogCompositeBowlShape?.errorMessage} hasError={errors.compositeDogCompositeBowlShape?.hasError} ref={compositeDogCompositeBowlShapeRef} labelHidden={true} {...getOverrideProps(overrides, \\"compositeDogCompositeBowlShape\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || compositeDogModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || compositeDogModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render scalar relationship fields if overrides 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { CompositeDog, CompositeOwner as CompositeOwner0, CompositeToy, CompositeVet } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateCompositeDogFormInputValues = { name?: string; description?: string; CompositeOwner?: CompositeOwner0; CompositeToys?: CompositeToy[]; CompositeVets?: CompositeVet[]; compositeDogCompositeBowlShape?: string; }; export declare type UpdateCompositeDogFormValidationValues = { name?: ValidationFunction<string>; description?: ValidationFunction<string>; CompositeOwner?: ValidationFunction<CompositeOwner0>; CompositeToys?: ValidationFunction<CompositeToy>; CompositeVets?: ValidationFunction<CompositeVet>; compositeDogCompositeBowlShape?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateCompositeDogFormOverridesProps = { UpdateCompositeDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; description?: PrimitiveOverrideProps<TextFieldProps>; CompositeOwner?: PrimitiveOverrideProps<AutocompleteProps>; CompositeToys?: PrimitiveOverrideProps<AutocompleteProps>; CompositeVets?: PrimitiveOverrideProps<AutocompleteProps>; compositeDogCompositeBowlShape?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateCompositeDogFormProps = React.PropsWithChildren<{ overrides?: UpdateCompositeDogFormOverridesProps | undefined | null; } & { id?: { name: string; description: string; }; compositeDog?: CompositeDog; onSubmit?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues; onSuccess?: (fields: UpdateCompositeDogFormInputValues) => void; onError?: (fields: UpdateCompositeDogFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues; onValidate?: UpdateCompositeDogFormValidationValues; } & React.CSSProperties>; export default function UpdateCompositeDogForm(props: UpdateCompositeDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render sectional elements 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Divider, Flex, Grid, Heading, Text, TextField, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; export default function CustomWithSectionalElements(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", }; const [name, setName] = React.useState(initialValues.name); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setErrors({}); }; const validations = { name: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); const modelFields = { name, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } await onSubmit(modelFields); }} {...getOverrideProps(overrides, \\"CustomWithSectionalElements\\")} {...rest} > <Heading level={2} children=\\"Create a Post\\" {...getOverrideProps(overrides, \\"myHeading\\")} ></Heading> <TextField label=\\"Label\\" value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <Text children=\\"Did you put your name above?\\" {...getOverrideProps(overrides, \\"myText\\")} ></Text> <Divider orientation=\\"horizontal\\" {...getOverrideProps(overrides, \\"myDivider\\")} ></Divider> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render sectional elements 2`] = ` "import * as React from \\"react\\"; import { DividerProps, GridProps, HeadingProps, TextFieldProps, TextProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CustomWithSectionalElementsInputValues = { name?: string; }; export declare type CustomWithSectionalElementsValidationValues = { name?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CustomWithSectionalElementsOverridesProps = { CustomWithSectionalElementsGrid?: PrimitiveOverrideProps<GridProps>; myHeading?: PrimitiveOverrideProps<HeadingProps>; name?: PrimitiveOverrideProps<TextFieldProps>; myText?: PrimitiveOverrideProps<TextProps>; myDivider?: PrimitiveOverrideProps<DividerProps>; } & EscapeHatchProps; export declare type CustomWithSectionalElementsProps = React.PropsWithChildren<{ overrides?: CustomWithSectionalElementsOverridesProps | undefined | null; } & { onSubmit: (fields: CustomWithSectionalElementsInputValues) => void; onCancel?: () => void; onChange?: (fields: CustomWithSectionalElementsInputValues) => CustomWithSectionalElementsInputValues; onValidate?: CustomWithSectionalElementsValidationValues; } & React.CSSProperties>; export default function CustomWithSectionalElements(props: CustomWithSectionalElementsProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required parent field 1:1 relationships - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Dog, Owner } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateDogForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", owner: undefined, }; const [name, setName] = React.useState(initialValues.name); const [owner, setOwner] = React.useState(initialValues.owner); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setOwner(initialValues.owner); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); setErrors({}); }; const [currentOwnerDisplayValue, setCurrentOwnerDisplayValue] = React.useState(\\"\\"); const [currentOwnerValue, setCurrentOwnerValue] = React.useState(undefined); const ownerRef = React.createRef(); const getIDValue = { owner: (r) => JSON.stringify({ id: r?.id }), }; const ownerIdSet = new Set( Array.isArray(owner) ? owner.map((r) => getIDValue.owner?.(r)) : getIDValue.owner?.(owner) ); const ownerRecords = useDataStoreBinding({ type: \\"collection\\", model: Owner, }).items; const getDisplayValue = { owner: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], owner: [{ type: \\"Required\\", validationMessage: \\"Owner is required.\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, owner, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const dog = await DataStore.save(new Dog(modelFields)); const promises = []; const ownerToLink = modelFields.owner; if (ownerToLink) { promises.push( DataStore.save( Owner.copyOf(ownerToLink, (updated) => { updated.Dog = dog; }) ) ); const dogToUnlink = await ownerToLink.Dog; if (dogToUnlink) { if (JSON.stringify(dogToUnlink) !== JSON.stringify(dog)) { throw Error( \`Owner \${ownerToLink.id} cannot be linked to Dog because it is already linked to another Dog.\` ); } } } await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, owner, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, owner: value, }; const result = onChange(modelFields); value = result?.owner ?? value; } setOwner(value); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentOwnerValue} label={\\"Owner\\"} items={owner ? [owner] : []} hasError={errors?.owner?.hasError} errorMessage={errors?.owner?.errorMessage} getBadgeText={getDisplayValue.owner} setFieldValue={(model) => { setCurrentOwnerDisplayValue( model ? getDisplayValue.owner(model) : \\"\\" ); setCurrentOwnerValue(model); }} inputFieldRef={ownerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Owner\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Owner\\" value={currentOwnerDisplayValue} options={ownerRecords .filter((r) => !ownerIdSet.has(getIDValue.owner?.(r))) .map((r) => ({ id: getIDValue.owner?.(r), label: getDisplayValue.owner?.(r), }))} onSelect={({ id, label }) => { setCurrentOwnerValue( ownerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentOwnerDisplayValue(label); runValidationTasks(\\"owner\\", label); }} onClear={() => { setCurrentOwnerDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.owner?.hasError) { runValidationTasks(\\"owner\\", value); } setCurrentOwnerDisplayValue(value); setCurrentOwnerValue(undefined); }} onBlur={() => runValidationTasks(\\"owner\\", currentOwnerDisplayValue)} errorMessage={errors.owner?.errorMessage} hasError={errors.owner?.hasError} ref={ownerRef} labelHidden={true} {...getOverrideProps(overrides, \\"owner\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required parent field 1:1 relationships - Create 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Owner } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateDogFormInputValues = { name?: string; owner?: Owner; }; export declare type CreateDogFormValidationValues = { name?: ValidationFunction<string>; owner?: ValidationFunction<Owner>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateDogFormOverridesProps = { CreateDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; owner?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateDogFormProps = React.PropsWithChildren<{ overrides?: CreateDogFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateDogFormInputValues) => CreateDogFormInputValues; onSuccess?: (fields: CreateDogFormInputValues) => void; onError?: (fields: CreateDogFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateDogFormInputValues) => CreateDogFormInputValues; onValidate?: CreateDogFormValidationValues; } & React.CSSProperties>; export default function CreateDogForm(props: CreateDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required parent field 1:1 relationships - Update 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Dog, Owner } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateDogForm(props) { const { id: idProp, dog: dogModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", owner: undefined, }; const [name, setName] = React.useState(initialValues.name); const [owner, setOwner] = React.useState(initialValues.owner); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = dogRecord ? { ...initialValues, ...dogRecord, owner } : initialValues; setName(cleanValues.name); setOwner(cleanValues.owner); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); setErrors({}); }; const [dogRecord, setDogRecord] = React.useState(dogModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Dog, idProp) : dogModelProp; setDogRecord(record); const ownerRecord = record ? await record.owner : undefined; setOwner(ownerRecord); }; queryData(); }, [idProp, dogModelProp]); React.useEffect(resetStateValues, [dogRecord, owner]); const [currentOwnerDisplayValue, setCurrentOwnerDisplayValue] = React.useState(\\"\\"); const [currentOwnerValue, setCurrentOwnerValue] = React.useState(undefined); const ownerRef = React.createRef(); const getIDValue = { owner: (r) => JSON.stringify({ id: r?.id }), }; const ownerIdSet = new Set( Array.isArray(owner) ? owner.map((r) => getIDValue.owner?.(r)) : getIDValue.owner?.(owner) ); const ownerRecords = useDataStoreBinding({ type: \\"collection\\", model: Owner, }).items; const getDisplayValue = { owner: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], owner: [{ type: \\"Required\\", validationMessage: \\"Owner is required.\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, owner, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const ownerToUnlink = await dogRecord.owner; if (ownerToUnlink) { promises.push( DataStore.save( Owner.copyOf(ownerToUnlink, (updated) => { updated.Dog = undefined; updated.ownerDogId = undefined; }) ) ); } const ownerToLink = modelFields.owner; if (ownerToLink) { promises.push( DataStore.save( Owner.copyOf(ownerToLink, (updated) => { updated.Dog = dogRecord; }) ) ); const dogToUnlink = await ownerToLink.Dog; if (dogToUnlink) { if (JSON.stringify(dogToUnlink) !== JSON.stringify(dogRecord)) { throw Error( \`Owner \${ownerToLink.id} cannot be linked to Dog because it is already linked to another Dog.\` ); } } } promises.push( DataStore.save( Dog.copyOf(dogRecord, (updated) => { Object.assign(updated, modelFields); if (!modelFields.owner) { updated.dogOwnerId = undefined; } }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateDogForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, owner, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, owner: value, }; const result = onChange(modelFields); value = result?.owner ?? value; } setOwner(value); setCurrentOwnerValue(undefined); setCurrentOwnerDisplayValue(\\"\\"); }} currentFieldValue={currentOwnerValue} label={\\"Owner\\"} items={owner ? [owner] : []} hasError={errors?.owner?.hasError} errorMessage={errors?.owner?.errorMessage} getBadgeText={getDisplayValue.owner} setFieldValue={(model) => { setCurrentOwnerDisplayValue( model ? getDisplayValue.owner(model) : \\"\\" ); setCurrentOwnerValue(model); }} inputFieldRef={ownerRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Owner\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Owner\\" value={currentOwnerDisplayValue} options={ownerRecords .filter((r) => !ownerIdSet.has(getIDValue.owner?.(r))) .map((r) => ({ id: getIDValue.owner?.(r), label: getDisplayValue.owner?.(r), }))} onSelect={({ id, label }) => { setCurrentOwnerValue( ownerRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentOwnerDisplayValue(label); runValidationTasks(\\"owner\\", label); }} onClear={() => { setCurrentOwnerDisplayValue(\\"\\"); }} defaultValue={owner} onChange={(e) => { let { value } = e.target; if (errors.owner?.hasError) { runValidationTasks(\\"owner\\", value); } setCurrentOwnerDisplayValue(value); setCurrentOwnerValue(undefined); }} onBlur={() => runValidationTasks(\\"owner\\", currentOwnerDisplayValue)} errorMessage={errors.owner?.errorMessage} hasError={errors.owner?.hasError} ref={ownerRef} labelHidden={true} {...getOverrideProps(overrides, \\"owner\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || dogModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || dogModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required parent field 1:1 relationships - Update 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Dog, Owner } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateDogFormInputValues = { name?: string; owner?: Owner; }; export declare type UpdateDogFormValidationValues = { name?: ValidationFunction<string>; owner?: ValidationFunction<Owner>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateDogFormOverridesProps = { UpdateDogFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; owner?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateDogFormProps = React.PropsWithChildren<{ overrides?: UpdateDogFormOverridesProps | undefined | null; } & { id?: string; dog?: Dog; onSubmit?: (fields: UpdateDogFormInputValues) => UpdateDogFormInputValues; onSuccess?: (fields: UpdateDogFormInputValues) => void; onError?: (fields: UpdateDogFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateDogFormInputValues) => UpdateDogFormInputValues; onValidate?: UpdateDogFormValidationValues; } & React.CSSProperties>; export default function UpdateDogForm(props: UpdateDogFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required related field 1:1 relationships - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Owner, Dog as Dog0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function CreateOwnerForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Dog: undefined, }; const [name, setName] = React.useState(initialValues.name); const [Dog, setDog] = React.useState(initialValues.Dog); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setDog(initialValues.Dog); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); const [currentDogValue, setCurrentDogValue] = React.useState(undefined); const DogRef = React.createRef(); const getIDValue = { Dog: (r) => JSON.stringify({ id: r?.id }), }; const DogIdSet = new Set( Array.isArray(Dog) ? Dog.map((r) => getIDValue.Dog?.(r)) : getIDValue.Dog?.(Dog) ); const dogRecords = useDataStoreBinding({ type: \\"collection\\", model: Dog0, }).items; const getDisplayValue = { Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], Dog: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const owner = await DataStore.save(new Owner(modelFields)); const promises = []; const dogToLink = modelFields.Dog; if (dogToLink) { promises.push( DataStore.save( Dog0.copyOf(dogToLink, (updated) => { updated.owner = owner; }) ) ); const ownerToUnlink = await dogToLink.owner; if (ownerToUnlink) { promises.push( DataStore.save( Owner.copyOf(ownerToUnlink, (updated) => { updated.Dog = undefined; updated.ownerDogId = undefined; }) ) ); } } await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateOwnerForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Dog, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, Dog: value, }; const result = onChange(modelFields); value = result?.Dog ?? value; } setDog(value); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); }} currentFieldValue={currentDogValue} label={\\"Dog\\"} items={Dog ? [Dog] : []} hasError={errors?.Dog?.hasError} errorMessage={errors?.Dog?.errorMessage} getBadgeText={getDisplayValue.Dog} setFieldValue={(model) => { setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); setCurrentDogValue(model); }} inputFieldRef={DogRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Dog\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Dog\\" value={currentDogDisplayValue} options={dogRecords .filter((r) => !DogIdSet.has(getIDValue.Dog?.(r))) .map((r) => ({ id: getIDValue.Dog?.(r), label: getDisplayValue.Dog?.(r), }))} onSelect={({ id, label }) => { setCurrentDogValue( dogRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentDogDisplayValue(label); runValidationTasks(\\"Dog\\", label); }} onClear={() => { setCurrentDogDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Dog?.hasError) { runValidationTasks(\\"Dog\\", value); } setCurrentDogDisplayValue(value); setCurrentDogValue(undefined); }} onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} errorMessage={errors.Dog?.errorMessage} hasError={errors.Dog?.hasError} ref={DogRef} labelHidden={true} {...getOverrideProps(overrides, \\"Dog\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required related field 1:1 relationships - Create 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Dog as Dog0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type CreateOwnerFormInputValues = { name?: string; Dog?: Dog0; }; export declare type CreateOwnerFormValidationValues = { name?: ValidationFunction<string>; Dog?: ValidationFunction<Dog0>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type CreateOwnerFormOverridesProps = { CreateOwnerFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Dog?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type CreateOwnerFormProps = React.PropsWithChildren<{ overrides?: CreateOwnerFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onSuccess?: (fields: CreateOwnerFormInputValues) => void; onError?: (fields: CreateOwnerFormInputValues, errorMessage: string) => void; onChange?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; onValidate?: CreateOwnerFormValidationValues; } & React.CSSProperties>; export default function CreateOwnerForm(props: CreateOwnerFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required related field 1:1 relationships - Update 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Owner, Dog as Dog0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateOwnerForm(props) { const { id: idProp, owner: ownerModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Dog: undefined, }; const [name, setName] = React.useState(initialValues.name); const [Dog, setDog] = React.useState(initialValues.Dog); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = ownerRecord ? { ...initialValues, ...ownerRecord, Dog } : initialValues; setName(cleanValues.name); setDog(cleanValues.Dog); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; const [ownerRecord, setOwnerRecord] = React.useState(ownerModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Owner, idProp) : ownerModelProp; setOwnerRecord(record); const DogRecord = record ? await record.Dog : undefined; setDog(DogRecord); }; queryData(); }, [idProp, ownerModelProp]); React.useEffect(resetStateValues, [ownerRecord, Dog]); const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); const [currentDogValue, setCurrentDogValue] = React.useState(undefined); const DogRef = React.createRef(); const getIDValue = { Dog: (r) => JSON.stringify({ id: r?.id }), }; const DogIdSet = new Set( Array.isArray(Dog) ? Dog.map((r) => getIDValue.Dog?.(r)) : getIDValue.Dog?.(Dog) ); const dogRecords = useDataStoreBinding({ type: \\"collection\\", model: Dog0, }).items; const getDisplayValue = { Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], Dog: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const dogToUnlink = await ownerRecord.Dog; if (dogToUnlink) { if ( JSON.stringify(dogToUnlink) !== JSON.stringify(modelFields.Dog) ) { throw Error( \`Dog \${dogToUnlink.id} cannot be unlinked because Dog requires Owner.\` ); } } const dogToLink = modelFields.Dog; if (dogToLink) { promises.push( DataStore.save( Dog0.copyOf(dogToLink, (updated) => { updated.owner = ownerRecord; }) ) ); const ownerToUnlink = await dogToLink.owner; if (ownerToUnlink) { promises.push( DataStore.save( Owner.copyOf(ownerToUnlink, (updated) => { updated.Dog = undefined; updated.ownerDogId = undefined; }) ) ); } } promises.push( DataStore.save( Owner.copyOf(ownerRecord, (updated) => { Object.assign(updated, modelFields); if (!modelFields.Dog) { updated.ownerDogId = undefined; } }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateOwnerForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Dog, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, Dog: value, }; const result = onChange(modelFields); value = result?.Dog ?? value; } setDog(value); setCurrentDogValue(undefined); setCurrentDogDisplayValue(\\"\\"); }} currentFieldValue={currentDogValue} label={\\"Dog\\"} items={Dog ? [Dog] : []} hasError={errors?.Dog?.hasError} errorMessage={errors?.Dog?.errorMessage} getBadgeText={getDisplayValue.Dog} setFieldValue={(model) => { setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); setCurrentDogValue(model); }} inputFieldRef={DogRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Dog\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Dog\\" value={currentDogDisplayValue} options={dogRecords .filter((r) => !DogIdSet.has(getIDValue.Dog?.(r))) .map((r) => ({ id: getIDValue.Dog?.(r), label: getDisplayValue.Dog?.(r), }))} onSelect={({ id, label }) => { setCurrentDogValue( dogRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentDogDisplayValue(label); runValidationTasks(\\"Dog\\", label); }} onClear={() => { setCurrentDogDisplayValue(\\"\\"); }} defaultValue={Dog} onChange={(e) => { let { value } = e.target; if (errors.Dog?.hasError) { runValidationTasks(\\"Dog\\", value); } setCurrentDogDisplayValue(value); setCurrentDogValue(undefined); }} onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} errorMessage={errors.Dog?.errorMessage} hasError={errors.Dog?.hasError} ref={DogRef} labelHidden={true} {...getOverrideProps(overrides, \\"Dog\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || ownerModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || ownerModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests custom form tests should render thrown error for required related field 1:1 relationships - Update 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Owner, Dog as Dog0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateOwnerFormInputValues = { name?: string; Dog?: Dog0; }; export declare type UpdateOwnerFormValidationValues = { name?: ValidationFunction<string>; Dog?: ValidationFunction<Dog0>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateOwnerFormOverridesProps = { UpdateOwnerFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Dog?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateOwnerFormProps = React.PropsWithChildren<{ overrides?: UpdateOwnerFormOverridesProps | undefined | null; } & { id?: string; owner?: Owner; onSubmit?: (fields: UpdateOwnerFormInputValues) => UpdateOwnerFormInputValues; onSuccess?: (fields: UpdateOwnerFormInputValues) => void; onError?: (fields: UpdateOwnerFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateOwnerFormInputValues) => UpdateOwnerFormInputValues; onValidate?: UpdateOwnerFormValidationValues; } & React.CSSProperties>; export default function UpdateOwnerForm(props: UpdateOwnerFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyPostForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { caption: \\"\\", username: \\"\\", post_url: \\"\\", metadata: \\"\\", profile_url: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [caption, setCaption] = React.useState(initialValues.caption); const [username, setUsername] = React.useState(initialValues.username); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setCaption(initialValues.caption); setUsername(initialValues.username); setPost_url(initialValues.post_url); setMetadata(initialValues.metadata); setProfile_url(initialValues.profile_url); setNonModelField(initialValues.nonModelField); setNonModelFieldArray(initialValues.nonModelFieldArray); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { caption: [], username: [], post_url: [{ type: \\"URL\\" }], metadata: [{ type: \\"JSON\\" }], profile_url: [{ type: \\"URL\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { caption, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { caption: modelFields.caption, username: modelFields.username, post_url: modelFields.post_url, metadata: modelFields.metadata, profile_url: modelFields.profile_url, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await DataStore.save(new Post(modelFieldsToSave)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption: value, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption, username: value, post_url, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Post url\\" isRequired={false} isReadOnly={false} value={post_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url: value, metadata, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} ></TextField> <TextAreaField label=\\"Metadata\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata: value, profile_url, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} ></TextAreaField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> <TextAreaField label=\\"Non model field\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { caption, username, post_url, metadata, profile_url, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Non model field array\\" isRequired={false} isReadOnly={false} value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} ></TextAreaField> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyPostFormInputValues = { caption?: string; username?: string; post_url?: string; metadata?: string; profile_url?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type MyPostFormValidationValues = { caption?: ValidationFunction<string>; username?: ValidationFunction<string>; post_url?: ValidationFunction<string>; metadata?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; nonModelField?: ValidationFunction<string>; nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyPostFormOverridesProps = { MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; username?: PrimitiveOverrideProps<TextFieldProps>; post_url?: PrimitiveOverrideProps<TextFieldProps>; metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; export declare type MyPostFormProps = React.PropsWithChildren<{ overrides?: MyPostFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onSuccess?: (fields: MyPostFormInputValues) => void; onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with array of Enums 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Tag, Post, TagPost } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { label: \\"\\", Posts: [], statuses: [], }; const [label, setLabel] = React.useState(initialValues.label); const [Posts, setPosts] = React.useState(initialValues.Posts); const [statuses, setStatuses] = React.useState(initialValues.statuses); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setLabel(initialValues.label); setPosts(initialValues.Posts); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); setStatuses(initialValues.statuses); setCurrentStatusesValue(\\"\\"); setErrors({}); }; const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); const PostsRef = React.createRef(); const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); const statusesRef = React.createRef(); const getIDValue = { Posts: (r) => JSON.stringify({ id: r?.id }), }; const PostsIdSet = new Set( Array.isArray(Posts) ? Posts.map((r) => getIDValue.Posts?.(r)) : getIDValue.Posts?.(Posts) ); const postRecords = useDataStoreBinding({ type: \\"collection\\", model: Post, }).items; const getDisplayValue = { Posts: (r) => r?.title, statuses: (r) => { const enumDisplayValueMap = { PENDING: \\"Pending\\", POSTED: \\"Posted\\", IN_REVIEW: \\"In review\\", }; return enumDisplayValueMap[r]; }, }; const validations = { label: [], Posts: [], statuses: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { label, Posts, statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { label: modelFields.label, statuses: modelFields.statuses, }; const tag = await DataStore.save(new Tag(modelFieldsToSave)); const promises = []; promises.push( ...Posts.reduce((promises, post) => { promises.push( DataStore.save( new TagPost({ tag, post, }) ) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"TagCreateForm\\")} {...rest} > <TextField label=\\"Label\\" isRequired={false} isReadOnly={false} value={label} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { label: value, Posts, statuses, }; const result = onChange(modelFields); value = result?.label ?? value; } if (errors.label?.hasError) { runValidationTasks(\\"label\\", value); } setLabel(value); }} onBlur={() => runValidationTasks(\\"label\\", label)} errorMessage={errors.label?.errorMessage} hasError={errors.label?.hasError} {...getOverrideProps(overrides, \\"label\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts: values, statuses, }; const result = onChange(modelFields); values = result?.Posts ?? values; } setPosts(values); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); }} currentFieldValue={currentPostsValue} label={\\"Posts\\"} items={Posts} hasError={errors?.Posts?.hasError} errorMessage={errors?.Posts?.errorMessage} getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { setCurrentPostsDisplayValue( model ? getDisplayValue.Posts(model) : \\"\\" ); setCurrentPostsValue(model); }} inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Posts\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostsDisplayValue} options={postRecords .filter((r) => !PostsIdSet.has(getIDValue.Posts?.(r))) .map((r) => ({ id: getIDValue.Posts?.(r), label: getDisplayValue.Posts?.(r), }))} onSelect={({ id, label }) => { setCurrentPostsValue( postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostsDisplayValue(label); runValidationTasks(\\"Posts\\", label); }} onClear={() => { setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Posts?.hasError) { runValidationTasks(\\"Posts\\", value); } setCurrentPostsDisplayValue(value); setCurrentPostsValue(undefined); }} onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} errorMessage={errors.Posts?.errorMessage} hasError={errors.Posts?.hasError} ref={PostsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Posts\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts, statuses: values, }; const result = onChange(modelFields); values = result?.statuses ?? values; } setStatuses(values); setCurrentStatusesValue(\\"\\"); }} currentFieldValue={currentStatusesValue} label={\\"Statuses\\"} items={statuses} hasError={errors?.statuses?.hasError} errorMessage={errors?.statuses?.errorMessage} getBadgeText={getDisplayValue.statuses} setFieldValue={setCurrentStatusesValue} inputFieldRef={statusesRef} defaultFieldValue={\\"\\"} > <SelectField label=\\"Statuses\\" placeholder=\\"Please select an option\\" isDisabled={false} value={currentStatusesValue} onChange={(e) => { let { value } = e.target; if (errors.statuses?.hasError) { runValidationTasks(\\"statuses\\", value); } setCurrentStatusesValue(value); }} onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} errorMessage={errors.statuses?.errorMessage} hasError={errors.statuses?.hasError} ref={statusesRef} labelHidden={true} {...getOverrideProps(overrides, \\"statuses\\")} > <option children=\\"Pending\\" value=\\"PENDING\\" {...getOverrideProps(overrides, \\"statusesoption0\\")} ></option> <option children=\\"Posted\\" value=\\"POSTED\\" {...getOverrideProps(overrides, \\"statusesoption1\\")} ></option> <option children=\\"In review\\" value=\\"IN_REVIEW\\" {...getOverrideProps(overrides, \\"statusesoption2\\")} ></option> </SelectField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with array of Enums 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type TagCreateFormInputValues = { label?: string; Posts?: Post[]; statuses?: string[]; }; export declare type TagCreateFormValidationValues = { label?: ValidationFunction<string>; Posts?: ValidationFunction<Post>; statuses?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type TagCreateFormOverridesProps = { TagCreateFormGrid?: PrimitiveOverrideProps<GridProps>; label?: PrimitiveOverrideProps<TextFieldProps>; Posts?: PrimitiveOverrideProps<AutocompleteProps>; statuses?: PrimitiveOverrideProps<SelectFieldProps>; } & EscapeHatchProps; export declare type TagCreateFormProps = React.PropsWithChildren<{ overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onSuccess?: (fields: TagCreateFormInputValues) => void; onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Member, Team as Team0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", teamID: undefined, Team: undefined, }; const [name, setName] = React.useState(initialValues.name); const [teamID, setTeamID] = React.useState(initialValues.teamID); const [Team, setTeam] = React.useState(initialValues.Team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setTeamID(initialValues.teamID); setCurrentTeamIDValue(undefined); setCurrentTeamIDDisplayValue(\\"\\"); setTeam(initialValues.Team); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = React.useState(\\"\\"); const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); const teamIDRef = React.createRef(); const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(\\"\\"); const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); const TeamRef = React.createRef(); const getIDValue = { Team: (r) => JSON.stringify({ id: r?.id }), }; const TeamIdSet = new Set( Array.isArray(Team) ? Team.map((r) => getIDValue.Team?.(r)) : getIDValue.Team?.(Team) ); const teamRecords = useDataStoreBinding({ type: \\"collection\\", model: Team0, }).items; const getDisplayValue = { teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Team: (r) => r?.name, }; const validations = { name: [], teamID: [{ type: \\"Required\\" }], Team: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, teamID, Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Member(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyMemberForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, teamID, Team, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID: value, Team, }; const result = onChange(modelFields); value = result?.teamID ?? value; } setTeamID(value); setCurrentTeamIDValue(undefined); }} currentFieldValue={currentTeamIDValue} label={\\"Team id\\"} items={teamID ? [teamID] : []} hasError={errors?.teamID?.hasError} errorMessage={errors?.teamID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentTeamIDDisplayValue( value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" ); setCurrentTeamIDValue(value); }} inputFieldRef={teamIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamIDDisplayValue} options={teamRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.teamID?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamIDValue(id); setCurrentTeamIDDisplayValue(label); runValidationTasks(\\"teamID\\", label); }} onClear={() => { setCurrentTeamIDDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.teamID?.hasError) { runValidationTasks(\\"teamID\\", value); } setCurrentTeamIDDisplayValue(value); setCurrentTeamIDValue(undefined); }} onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} errorMessage={errors.teamID?.errorMessage} hasError={errors.teamID?.hasError} ref={teamIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"teamID\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID, Team: value, }; const result = onChange(modelFields); value = result?.Team ?? value; } setTeam(value); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); }} currentFieldValue={currentTeamValue} label={\\"Team Label\\"} items={Team ? [Team] : []} hasError={errors?.Team?.hasError} errorMessage={errors?.Team?.errorMessage} getBadgeText={getDisplayValue.Team} setFieldValue={(model) => { setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); setCurrentTeamValue(model); }} inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team Label\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamDisplayValue} options={teamRecords .filter((r) => !TeamIdSet.has(getIDValue.Team?.(r))) .map((r) => ({ id: getIDValue.Team?.(r), label: getDisplayValue.Team?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamValue( teamRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentTeamDisplayValue(label); runValidationTasks(\\"Team\\", label); }} onClear={() => { setCurrentTeamDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Team?.hasError) { runValidationTasks(\\"Team\\", value); } setCurrentTeamDisplayValue(value); setCurrentTeamValue(undefined); }} onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} errorMessage={errors.Team?.errorMessage} hasError={errors.Team?.hasError} ref={TeamRef} labelHidden={true} {...getOverrideProps(overrides, \\"Team\\")} ></Autocomplete> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Team as Team0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyMemberFormInputValues = { name?: string; teamID?: string; Team?: Team0; }; export declare type MyMemberFormValidationValues = { name?: ValidationFunction<string>; teamID?: ValidationFunction<string>; Team?: ValidationFunction<Team0>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyMemberFormOverridesProps = { MyMemberFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; teamID?: PrimitiveOverrideProps<AutocompleteProps>; Team?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type MyMemberFormProps = React.PropsWithChildren<{ overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onSuccess?: (fields: MyMemberFormInputValues) => void; onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { School, Student } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function SchoolCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Students: [], }; const [name, setName] = React.useState(initialValues.name); const [Students, setStudents] = React.useState(initialValues.Students); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setStudents(initialValues.Students); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); setErrors({}); }; const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = React.useState(\\"\\"); const [currentStudentsValue, setCurrentStudentsValue] = React.useState(undefined); const StudentsRef = React.createRef(); const getIDValue = { Students: (r) => JSON.stringify({ id: r?.id }), }; const StudentsIdSet = new Set( Array.isArray(Students) ? Students.map((r) => getIDValue.Students?.(r)) : getIDValue.Students?.(Students) ); const studentRecords = useDataStoreBinding({ type: \\"collection\\", model: Student, }).items; const getDisplayValue = { Students: (r) => r?.name, }; const validations = { name: [], Students: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Students, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { name: modelFields.name, }; const school = await DataStore.save(new School(modelFieldsToSave)); const promises = []; promises.push( ...Students.reduce((promises, original) => { promises.push( DataStore.save( Student.copyOf(original, (updated) => { updated.schoolID = school.id; }) ) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Students, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, Students: values, }; const result = onChange(modelFields); values = result?.Students ?? values; } setStudents(values); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); }} currentFieldValue={currentStudentsValue} label={\\"Students\\"} items={Students} hasError={errors?.Students?.hasError} errorMessage={errors?.Students?.errorMessage} getBadgeText={getDisplayValue.Students} setFieldValue={(model) => { setCurrentStudentsDisplayValue( model ? getDisplayValue.Students(model) : \\"\\" ); setCurrentStudentsValue(model); }} inputFieldRef={StudentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Students\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Student\\" value={currentStudentsDisplayValue} options={studentRecords .filter((r) => !StudentsIdSet.has(getIDValue.Students?.(r))) .map((r) => ({ id: getIDValue.Students?.(r), label: getDisplayValue.Students?.(r), }))} onSelect={({ id, label }) => { setCurrentStudentsValue( studentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentStudentsDisplayValue(label); runValidationTasks(\\"Students\\", label); }} onClear={() => { setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Students?.hasError) { runValidationTasks(\\"Students\\", value); } setCurrentStudentsDisplayValue(value); setCurrentStudentsValue(undefined); }} onBlur={() => runValidationTasks(\\"Students\\", currentStudentsDisplayValue) } errorMessage={errors.Students?.errorMessage} hasError={errors.Students?.hasError} ref={StudentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Students\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Student } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type SchoolCreateFormInputValues = { name?: string; Students?: Student[]; }; export declare type SchoolCreateFormValidationValues = { name?: ValidationFunction<string>; Students?: ValidationFunction<Student>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type SchoolCreateFormOverridesProps = { SchoolCreateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Students?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type SchoolCreateFormProps = React.PropsWithChildren<{ overrides?: SchoolCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; onSuccess?: (fields: SchoolCreateFormInputValues) => void; onError?: (fields: SchoolCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; onValidate?: SchoolCreateFormValidationValues; } & React.CSSProperties>; export default function SchoolCreateForm(props: SchoolCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Book, Author } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", primaryAuthor: undefined, }; const [name, setName] = React.useState(initialValues.name); const [primaryAuthor, setPrimaryAuthor] = React.useState( initialValues.primaryAuthor ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPrimaryAuthor(initialValues.primaryAuthor); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); setErrors({}); }; const [ currentPrimaryAuthorDisplayValue, setCurrentPrimaryAuthorDisplayValue, ] = React.useState(\\"\\"); const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); const primaryAuthorRef = React.createRef(); const getIDValue = { primaryAuthor: (r) => JSON.stringify({ id: r?.id }), }; const primaryAuthorIdSet = new Set( Array.isArray(primaryAuthor) ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) : getIDValue.primaryAuthor?.(primaryAuthor) ); const authorRecords = useDataStoreBinding({ type: \\"collection\\", model: Author, }).items; const getDisplayValue = { primaryAuthor: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], primaryAuthor: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, primaryAuthor, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Book(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, primaryAuthor, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor: value, }; const result = onChange(modelFields); value = result?.primaryAuthor ?? value; } setPrimaryAuthor(value); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} items={primaryAuthor ? [primaryAuthor] : []} hasError={errors?.primaryAuthor?.hasError} errorMessage={errors?.primaryAuthor?.errorMessage} getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { setCurrentPrimaryAuthorDisplayValue( model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); setCurrentPrimaryAuthorValue(model); }} inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Primary author\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Author\\" value={currentPrimaryAuthorDisplayValue} options={authorRecords .filter( (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) ) .map((r) => ({ id: getIDValue.primaryAuthor?.(r), label: getDisplayValue.primaryAuthor?.(r), }))} onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( authorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryAuthorDisplayValue(label); runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.primaryAuthor?.hasError) { runValidationTasks(\\"primaryAuthor\\", value); } setCurrentPrimaryAuthorDisplayValue(value); setCurrentPrimaryAuthorValue(undefined); }} onBlur={() => runValidationTasks( \\"primaryAuthor\\", currentPrimaryAuthorDisplayValue ) } errorMessage={errors.primaryAuthor?.errorMessage} hasError={errors.primaryAuthor?.hasError} ref={primaryAuthorRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryAuthor\\")} ></Autocomplete> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Author } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type BookCreateFormInputValues = { name?: string; primaryAuthor?: Author; }; export declare type BookCreateFormValidationValues = { name?: ValidationFunction<string>; primaryAuthor?: ValidationFunction<Author>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type BookCreateFormOverridesProps = { BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type BookCreateFormProps = React.PropsWithChildren<{ overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onSuccess?: (fields: BookCreateFormInputValues) => void; onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with manyToMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Tag, Post, TagPost } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { label: \\"\\", Posts: [], statuses: [], }; const [label, setLabel] = React.useState(initialValues.label); const [Posts, setPosts] = React.useState(initialValues.Posts); const [statuses, setStatuses] = React.useState(initialValues.statuses); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setLabel(initialValues.label); setPosts(initialValues.Posts); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); setStatuses(initialValues.statuses); setCurrentStatusesValue(\\"\\"); setErrors({}); }; const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); const PostsRef = React.createRef(); const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); const statusesRef = React.createRef(); const getIDValue = { Posts: (r) => JSON.stringify({ id: r?.id }), }; const PostsIdSet = new Set( Array.isArray(Posts) ? Posts.map((r) => getIDValue.Posts?.(r)) : getIDValue.Posts?.(Posts) ); const postRecords = useDataStoreBinding({ type: \\"collection\\", model: Post, }).items; const getDisplayValue = { Posts: (r) => r?.title, statuses: (r) => { const enumDisplayValueMap = { PENDING: \\"Pending\\", POSTED: \\"Posted\\", IN_REVIEW: \\"In review\\", }; return enumDisplayValueMap[r]; }, }; const validations = { label: [], Posts: [], statuses: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { label, Posts, statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { label: modelFields.label, statuses: modelFields.statuses, }; const tag = await DataStore.save(new Tag(modelFieldsToSave)); const promises = []; promises.push( ...Posts.reduce((promises, post) => { promises.push( DataStore.save( new TagPost({ tag, post, }) ) ); return promises; }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"TagCreateForm\\")} {...rest} > <TextField label=\\"Label\\" isRequired={false} isReadOnly={false} value={label} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { label: value, Posts, statuses, }; const result = onChange(modelFields); value = result?.label ?? value; } if (errors.label?.hasError) { runValidationTasks(\\"label\\", value); } setLabel(value); }} onBlur={() => runValidationTasks(\\"label\\", label)} errorMessage={errors.label?.errorMessage} hasError={errors.label?.hasError} {...getOverrideProps(overrides, \\"label\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts: values, statuses, }; const result = onChange(modelFields); values = result?.Posts ?? values; } setPosts(values); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); }} currentFieldValue={currentPostsValue} label={\\"Posts\\"} items={Posts} hasError={errors?.Posts?.hasError} errorMessage={errors?.Posts?.errorMessage} getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { setCurrentPostsDisplayValue( model ? getDisplayValue.Posts(model) : \\"\\" ); setCurrentPostsValue(model); }} inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Posts\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostsDisplayValue} options={postRecords .filter((r) => !PostsIdSet.has(getIDValue.Posts?.(r))) .map((r) => ({ id: getIDValue.Posts?.(r), label: getDisplayValue.Posts?.(r), }))} onSelect={({ id, label }) => { setCurrentPostsValue( postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostsDisplayValue(label); runValidationTasks(\\"Posts\\", label); }} onClear={() => { setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Posts?.hasError) { runValidationTasks(\\"Posts\\", value); } setCurrentPostsDisplayValue(value); setCurrentPostsValue(undefined); }} onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} errorMessage={errors.Posts?.errorMessage} hasError={errors.Posts?.hasError} ref={PostsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Posts\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts, statuses: values, }; const result = onChange(modelFields); values = result?.statuses ?? values; } setStatuses(values); setCurrentStatusesValue(\\"\\"); }} currentFieldValue={currentStatusesValue} label={\\"Statuses\\"} items={statuses} hasError={errors?.statuses?.hasError} errorMessage={errors?.statuses?.errorMessage} getBadgeText={getDisplayValue.statuses} setFieldValue={setCurrentStatusesValue} inputFieldRef={statusesRef} defaultFieldValue={\\"\\"} > <SelectField label=\\"Statuses\\" placeholder=\\"Please select an option\\" isDisabled={false} value={currentStatusesValue} onChange={(e) => { let { value } = e.target; if (errors.statuses?.hasError) { runValidationTasks(\\"statuses\\", value); } setCurrentStatusesValue(value); }} onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} errorMessage={errors.statuses?.errorMessage} hasError={errors.statuses?.hasError} ref={statusesRef} labelHidden={true} {...getOverrideProps(overrides, \\"statuses\\")} > <option children=\\"Pending\\" value=\\"PENDING\\" {...getOverrideProps(overrides, \\"statusesoption0\\")} ></option> <option children=\\"Posted\\" value=\\"POSTED\\" {...getOverrideProps(overrides, \\"statusesoption1\\")} ></option> <option children=\\"In review\\" value=\\"IN_REVIEW\\" {...getOverrideProps(overrides, \\"statusesoption2\\")} ></option> </SelectField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with manyToMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type TagCreateFormInputValues = { label?: string; Posts?: Post[]; statuses?: string[]; }; export declare type TagCreateFormValidationValues = { label?: ValidationFunction<string>; Posts?: ValidationFunction<Post>; statuses?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type TagCreateFormOverridesProps = { TagCreateFormGrid?: PrimitiveOverrideProps<GridProps>; label?: PrimitiveOverrideProps<TextFieldProps>; Posts?: PrimitiveOverrideProps<AutocompleteProps>; statuses?: PrimitiveOverrideProps<SelectFieldProps>; } & EscapeHatchProps; export declare type TagCreateFormProps = React.PropsWithChildren<{ overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onSuccess?: (fields: TagCreateFormInputValues) => void; onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Book, Author, Title } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", primaryAuthor: undefined, primaryTitle: undefined, }; const [name, setName] = React.useState(initialValues.name); const [primaryAuthor, setPrimaryAuthor] = React.useState( initialValues.primaryAuthor ); const [primaryTitle, setPrimaryTitle] = React.useState( initialValues.primaryTitle ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPrimaryAuthor(initialValues.primaryAuthor); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); setPrimaryTitle(initialValues.primaryTitle); setCurrentPrimaryTitleValue(undefined); setCurrentPrimaryTitleDisplayValue(\\"\\"); setErrors({}); }; const [ currentPrimaryAuthorDisplayValue, setCurrentPrimaryAuthorDisplayValue, ] = React.useState(\\"\\"); const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); const primaryAuthorRef = React.createRef(); const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = React.useState(\\"\\"); const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = React.useState(undefined); const primaryTitleRef = React.createRef(); const getIDValue = { primaryAuthor: (r) => JSON.stringify({ id: r?.id }), primaryTitle: (r) => JSON.stringify({ id: r?.id }), }; const primaryAuthorIdSet = new Set( Array.isArray(primaryAuthor) ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) : getIDValue.primaryAuthor?.(primaryAuthor) ); const primaryTitleIdSet = new Set( Array.isArray(primaryTitle) ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) : getIDValue.primaryTitle?.(primaryTitle) ); const authorRecords = useDataStoreBinding({ type: \\"collection\\", model: Author, }).items; const titleRecords = useDataStoreBinding({ type: \\"collection\\", model: Title, }).items; const getDisplayValue = { primaryAuthor: (r) => r?.name, primaryTitle: (r) => r?.name, }; const validations = { name: [], primaryAuthor: [], primaryTitle: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, primaryAuthor, primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Book(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, primaryAuthor, primaryTitle, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor: value, primaryTitle, }; const result = onChange(modelFields); value = result?.primaryAuthor ?? value; } setPrimaryAuthor(value); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} items={primaryAuthor ? [primaryAuthor] : []} hasError={errors?.primaryAuthor?.hasError} errorMessage={errors?.primaryAuthor?.errorMessage} getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { setCurrentPrimaryAuthorDisplayValue( model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); setCurrentPrimaryAuthorValue(model); }} inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Primary author\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Author\\" value={currentPrimaryAuthorDisplayValue} options={authorRecords .filter( (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) ) .map((r) => ({ id: getIDValue.primaryAuthor?.(r), label: getDisplayValue.primaryAuthor?.(r), }))} onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( authorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryAuthorDisplayValue(label); runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.primaryAuthor?.hasError) { runValidationTasks(\\"primaryAuthor\\", value); } setCurrentPrimaryAuthorDisplayValue(value); setCurrentPrimaryAuthorValue(undefined); }} onBlur={() => runValidationTasks( \\"primaryAuthor\\", currentPrimaryAuthorDisplayValue ) } errorMessage={errors.primaryAuthor?.errorMessage} hasError={errors.primaryAuthor?.hasError} ref={primaryAuthorRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryAuthor\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, primaryAuthor, primaryTitle: value, }; const result = onChange(modelFields); value = result?.primaryTitle ?? value; } setPrimaryTitle(value); setCurrentPrimaryTitleValue(undefined); setCurrentPrimaryTitleDisplayValue(\\"\\"); }} currentFieldValue={currentPrimaryTitleValue} label={\\"Primary title\\"} items={primaryTitle ? [primaryTitle] : []} hasError={errors?.primaryTitle?.hasError} errorMessage={errors?.primaryTitle?.errorMessage} getBadgeText={getDisplayValue.primaryTitle} setFieldValue={(model) => { setCurrentPrimaryTitleDisplayValue( model ? getDisplayValue.primaryTitle(model) : \\"\\" ); setCurrentPrimaryTitleValue(model); }} inputFieldRef={primaryTitleRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Primary title\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Title\\" value={currentPrimaryTitleDisplayValue} options={titleRecords .filter((r) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(r))) .map((r) => ({ id: getIDValue.primaryTitle?.(r), label: getDisplayValue.primaryTitle?.(r), }))} onSelect={({ id, label }) => { setCurrentPrimaryTitleValue( titleRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPrimaryTitleDisplayValue(label); runValidationTasks(\\"primaryTitle\\", label); }} onClear={() => { setCurrentPrimaryTitleDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.primaryTitle?.hasError) { runValidationTasks(\\"primaryTitle\\", value); } setCurrentPrimaryTitleDisplayValue(value); setCurrentPrimaryTitleValue(undefined); }} onBlur={() => runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) } errorMessage={errors.primaryTitle?.errorMessage} hasError={errors.primaryTitle?.hasError} ref={primaryTitleRef} labelHidden={true} {...getOverrideProps(overrides, \\"primaryTitle\\")} ></Autocomplete> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Author, Title } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type BookCreateFormInputValues = { name?: string; primaryAuthor?: Author; primaryTitle?: Title; }; export declare type BookCreateFormValidationValues = { name?: ValidationFunction<string>; primaryAuthor?: ValidationFunction<Author>; primaryTitle?: ValidationFunction<Title>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type BookCreateFormOverridesProps = { BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type BookCreateFormProps = React.PropsWithChildren<{ overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onSuccess?: (fields: BookCreateFormInputValues) => void; onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyPostForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { TextAreaFieldbbd63464: \\"\\", caption: \\"\\", username: \\"\\", profile_url: \\"\\", post_url: \\"\\", metadata: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( initialValues.TextAreaFieldbbd63464 ); const [caption, setCaption] = React.useState(initialValues.caption); const [username, setUsername] = React.useState(initialValues.username); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord ? { ...initialValues, ...postRecord } : initialValues; setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); setCaption(cleanValues.caption); setUsername(cleanValues.username); setProfile_url(cleanValues.profile_url); setPost_url(cleanValues.post_url); setMetadata( typeof cleanValues.metadata === \\"string\\" || cleanValues.metadata === null ? cleanValues.metadata : JSON.stringify(cleanValues.metadata) ); setNonModelField( typeof cleanValues.nonModelField === \\"string\\" || cleanValues.nonModelField === null ? cleanValues.nonModelField : JSON.stringify(cleanValues.nonModelField) ); setNonModelFieldArray( cleanValues.nonModelFieldArray?.map((item) => typeof item === \\"string\\" ? item : JSON.stringify(item) ) ?? [] ); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Post, idProp) : postModelProp; setPostRecord(record); }; queryData(); }, [idProp, postModelProp]); React.useEffect(resetStateValues, [postRecord]); const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { TextAreaFieldbbd63464: [], caption: [], username: [], profile_url: [{ type: \\"URL\\" }], post_url: [{ type: \\"URL\\" }], metadata: [{ type: \\"JSON\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { caption: modelFields.caption, username: modelFields.username, profile_url: modelFields.profile_url, post_url: modelFields.post_url, metadata: modelFields.metadata, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await DataStore.save( Post.copyOf(postRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextAreaField label=\\"Label\\" value={TextAreaFieldbbd63464} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464: value, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.TextAreaFieldbbd63464 ?? value; } if (errors.TextAreaFieldbbd63464?.hasError) { runValidationTasks(\\"TextAreaFieldbbd63464\\", value); } setTextAreaFieldbbd63464(value); }} onBlur={() => runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) } errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} hasError={errors.TextAreaFieldbbd63464?.hasError} {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} ></TextAreaField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption: value, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username: value, profile_url, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url: value, post_url, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> <TextField label=\\"Post url\\" isRequired={false} isReadOnly={false} value={post_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url: value, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} ></TextField> <TextAreaField label=\\"Metadata\\" isRequired={false} isReadOnly={false} value={metadata} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} ></TextAreaField> <TextAreaField label=\\"Non model field\\" isRequired={false} isReadOnly={false} value={nonModelField} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { TextAreaFieldbbd63464, caption, username, profile_url, post_url, metadata, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Non model field array\\" isRequired={false} isReadOnly={false} value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || postModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || postModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a update form 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyPostFormInputValues = { TextAreaFieldbbd63464?: string; caption?: string; username?: string; profile_url?: string; post_url?: string; metadata?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type MyPostFormValidationValues = { TextAreaFieldbbd63464?: ValidationFunction<string>; caption?: ValidationFunction<string>; username?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; post_url?: ValidationFunction<string>; metadata?: ValidationFunction<string>; nonModelField?: ValidationFunction<string>; nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyPostFormOverridesProps = { MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; username?: PrimitiveOverrideProps<TextFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; post_url?: PrimitiveOverrideProps<TextFieldProps>; metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; export declare type MyPostFormProps = React.PropsWithChildren<{ overrides?: MyPostFormOverridesProps | undefined | null; } & { id?: string; post?: Post; onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onSuccess?: (fields: MyPostFormInputValues) => void; onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { School, Student } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function SchoolUpdateForm(props) { const { id: idProp, school: schoolModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Students: [], }; const [name, setName] = React.useState(initialValues.name); const [Students, setStudents] = React.useState(initialValues.Students); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = schoolRecord ? { ...initialValues, ...schoolRecord, Students: linkedStudents } : initialValues; setName(cleanValues.name); setStudents(cleanValues.Students ?? []); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); setErrors({}); }; const [schoolRecord, setSchoolRecord] = React.useState(schoolModelProp); const [linkedStudents, setLinkedStudents] = React.useState([]); const canUnlinkStudents = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(School, idProp) : schoolModelProp; setSchoolRecord(record); const linkedStudents = record ? await record.Students.toArray() : []; setLinkedStudents(linkedStudents); }; queryData(); }, [idProp, schoolModelProp]); React.useEffect(resetStateValues, [schoolRecord, linkedStudents]); const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = React.useState(\\"\\"); const [currentStudentsValue, setCurrentStudentsValue] = React.useState(undefined); const StudentsRef = React.createRef(); const getIDValue = { Students: (r) => JSON.stringify({ id: r?.id }), }; const StudentsIdSet = new Set( Array.isArray(Students) ? Students.map((r) => getIDValue.Students?.(r)) : getIDValue.Students?.(Students) ); const studentRecords = useDataStoreBinding({ type: \\"collection\\", model: Student, }).items; const getDisplayValue = { Students: (r) => r?.name, }; const validations = { name: [], Students: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Students, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const studentsToLink = []; const studentsToUnLink = []; const studentsSet = new Set(); const linkedStudentsSet = new Set(); Students.forEach((r) => studentsSet.add(getIDValue.Students?.(r))); linkedStudents.forEach((r) => linkedStudentsSet.add(getIDValue.Students?.(r)) ); linkedStudents.forEach((r) => { if (!studentsSet.has(getIDValue.Students?.(r))) { studentsToUnLink.push(r); } }); Students.forEach((r) => { if (!linkedStudentsSet.has(getIDValue.Students?.(r))) { studentsToLink.push(r); } }); studentsToUnLink.forEach((original) => { if (!canUnlinkStudents) { throw Error( \`Student \${original.id} cannot be unlinked from School because schoolID is a required field.\` ); } promises.push( DataStore.save( Student.copyOf(original, (updated) => { updated.schoolID = null; }) ) ); }); studentsToLink.forEach((original) => { promises.push( DataStore.save( Student.copyOf(original, (updated) => { updated.schoolID = schoolRecord.id; }) ) ); }); const modelFieldsToSave = { name: modelFields.name, }; promises.push( DataStore.save( School.copyOf(schoolRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"SchoolUpdateForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Students, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, Students: values, }; const result = onChange(modelFields); values = result?.Students ?? values; } setStudents(values); setCurrentStudentsValue(undefined); setCurrentStudentsDisplayValue(\\"\\"); }} currentFieldValue={currentStudentsValue} label={\\"Students\\"} items={Students} hasError={errors?.Students?.hasError} errorMessage={errors?.Students?.errorMessage} getBadgeText={getDisplayValue.Students} setFieldValue={(model) => { setCurrentStudentsDisplayValue( model ? getDisplayValue.Students(model) : \\"\\" ); setCurrentStudentsValue(model); }} inputFieldRef={StudentsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Students\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Student\\" value={currentStudentsDisplayValue} options={studentRecords .filter((r) => !StudentsIdSet.has(getIDValue.Students?.(r))) .map((r) => ({ id: getIDValue.Students?.(r), label: getDisplayValue.Students?.(r), }))} onSelect={({ id, label }) => { setCurrentStudentsValue( studentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentStudentsDisplayValue(label); runValidationTasks(\\"Students\\", label); }} onClear={() => { setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Students?.hasError) { runValidationTasks(\\"Students\\", value); } setCurrentStudentsDisplayValue(value); setCurrentStudentsValue(undefined); }} onBlur={() => runValidationTasks(\\"Students\\", currentStudentsDisplayValue) } errorMessage={errors.Students?.errorMessage} hasError={errors.Students?.hasError} ref={StudentsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Students\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || schoolModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || schoolModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { School, Student } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type SchoolUpdateFormInputValues = { name?: string; Students?: Student[]; }; export declare type SchoolUpdateFormValidationValues = { name?: ValidationFunction<string>; Students?: ValidationFunction<Student>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type SchoolUpdateFormOverridesProps = { SchoolUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Students?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type SchoolUpdateFormProps = React.PropsWithChildren<{ overrides?: SchoolUpdateFormOverridesProps | undefined | null; } & { id?: string; school?: School; onSubmit?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; onSuccess?: (fields: SchoolUpdateFormInputValues) => void; onError?: (fields: SchoolUpdateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; onValidate?: SchoolUpdateFormValidationValues; } & React.CSSProperties>; export default function SchoolUpdateForm(props: SchoolUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship with model name collision 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { School, Student as Student0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function SchoolUpdateForm(props) { const { id: idProp, school: schoolModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", Student: [], Students: undefined, }; const [name, setName] = React.useState(initialValues.name); const [Student, setStudent] = React.useState(initialValues.Student); const [Students, setStudents] = React.useState(initialValues.Students); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = schoolRecord ? { ...initialValues, ...schoolRecord, Student: linkedStudent } : initialValues; setName(cleanValues.name); setStudent(cleanValues.Student ?? []); setCurrentStudentValue(undefined); setCurrentStudentDisplayValue(\\"\\"); setStudents(cleanValues.Students); setErrors({}); }; const [schoolRecord, setSchoolRecord] = React.useState(schoolModelProp); const [linkedStudent, setLinkedStudent] = React.useState([]); const canUnlinkStudent = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(School, idProp) : schoolModelProp; setSchoolRecord(record); const linkedStudent = record ? await record.Student.toArray() : []; setLinkedStudent(linkedStudent); }; queryData(); }, [idProp, schoolModelProp]); React.useEffect(resetStateValues, [schoolRecord, linkedStudent]); const [currentStudentDisplayValue, setCurrentStudentDisplayValue] = React.useState(\\"\\"); const [currentStudentValue, setCurrentStudentValue] = React.useState(undefined); const StudentRef = React.createRef(); const getIDValue = { Student: (r) => JSON.stringify({ id: r?.id }), }; const StudentIdSet = new Set( Array.isArray(Student) ? Student.map((r) => getIDValue.Student?.(r)) : getIDValue.Student?.(Student) ); const studentRecords = useDataStoreBinding({ type: \\"collection\\", model: Student0, }).items; const getDisplayValue = { Student: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], Student: [], Students: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, Student, Students, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const studentToLink = []; const studentToUnLink = []; const studentSet = new Set(); const linkedStudentSet = new Set(); Student.forEach((r) => studentSet.add(getIDValue.Student?.(r))); linkedStudent.forEach((r) => linkedStudentSet.add(getIDValue.Student?.(r)) ); linkedStudent.forEach((r) => { if (!studentSet.has(getIDValue.Student?.(r))) { studentToUnLink.push(r); } }); Student.forEach((r) => { if (!linkedStudentSet.has(getIDValue.Student?.(r))) { studentToLink.push(r); } }); studentToUnLink.forEach((original) => { if (!canUnlinkStudent) { throw Error( \`Student \${original.id} cannot be unlinked from School because schoolID is a required field.\` ); } promises.push( DataStore.save( Student0.copyOf(original, (updated) => { updated.schoolID = null; }) ) ); }); studentToLink.forEach((original) => { promises.push( DataStore.save( Student0.copyOf(original, (updated) => { updated.schoolID = schoolRecord.id; }) ) ); }); const modelFieldsToSave = { name: modelFields.name, }; promises.push( DataStore.save( School.copyOf(schoolRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"SchoolUpdateForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, Student, Students, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, Student: values, Students, }; const result = onChange(modelFields); values = result?.Student ?? values; } setStudent(values); setCurrentStudentValue(undefined); setCurrentStudentDisplayValue(\\"\\"); }} currentFieldValue={currentStudentValue} label={\\"Student\\"} items={Student} hasError={errors?.Student?.hasError} errorMessage={errors?.Student?.errorMessage} getBadgeText={getDisplayValue.Student} setFieldValue={(model) => { setCurrentStudentDisplayValue( model ? getDisplayValue.Student(model) : \\"\\" ); setCurrentStudentValue(model); }} inputFieldRef={StudentRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Student\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Student\\" value={currentStudentDisplayValue} options={studentRecords .filter((r) => !StudentIdSet.has(getIDValue.Student?.(r))) .map((r) => ({ id: getIDValue.Student?.(r), label: getDisplayValue.Student?.(r), }))} onSelect={({ id, label }) => { setCurrentStudentValue( studentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentStudentDisplayValue(label); runValidationTasks(\\"Student\\", label); }} onClear={() => { setCurrentStudentDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Student?.hasError) { runValidationTasks(\\"Student\\", value); } setCurrentStudentDisplayValue(value); setCurrentStudentValue(undefined); }} onBlur={() => runValidationTasks(\\"Student\\", currentStudentDisplayValue) } errorMessage={errors.Student?.errorMessage} hasError={errors.Student?.hasError} ref={StudentRef} labelHidden={true} {...getOverrideProps(overrides, \\"Student\\")} ></Autocomplete> </ArrayField> <Autocomplete label=\\"Label\\" options={studentRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.Students?.(r), }))} onSelect={({ id, label }) => { setStudents(id); runValidationTasks(\\"Students\\", id); }} onClear={() => { setStudents(\\"\\"); }} defaultValue={Students} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name, Student, Students: value, }; const result = onChange(modelFields); value = result?.Students ?? value; } if (errors.Students?.hasError) { runValidationTasks(\\"Students\\", value); } setStudents(value); }} onBlur={() => runValidationTasks(\\"Students\\", Students)} errorMessage={errors.Students?.errorMessage} hasError={errors.Students?.hasError} labelHidden={false} {...getOverrideProps(overrides, \\"Students\\")} ></Autocomplete> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || schoolModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || schoolModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship with model name collision 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { School, Student as Student0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type SchoolUpdateFormInputValues = { name?: string; Student?: Student0[]; Students?: string; }; export declare type SchoolUpdateFormValidationValues = { name?: ValidationFunction<string>; Student?: ValidationFunction<Student0>; Students?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type SchoolUpdateFormOverridesProps = { SchoolUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; Student?: PrimitiveOverrideProps<AutocompleteProps>; Students?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type SchoolUpdateFormProps = React.PropsWithChildren<{ overrides?: SchoolUpdateFormOverridesProps | undefined | null; } & { id?: string; school?: School; onSubmit?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; onSuccess?: (fields: SchoolUpdateFormInputValues) => void; onError?: (fields: SchoolUpdateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; onValidate?: SchoolUpdateFormValidationValues; } & React.CSSProperties>; export default function SchoolUpdateForm(props: SchoolUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Member, Team as Team0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyMemberForm(props) { const { id: idProp, member: memberModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", teamID: undefined, Team: undefined, }; const [name, setName] = React.useState(initialValues.name); const [teamID, setTeamID] = React.useState(initialValues.teamID); const [Team, setTeam] = React.useState(initialValues.Team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = memberRecord ? { ...initialValues, ...memberRecord, teamID, Team } : initialValues; setName(cleanValues.name); setTeamID(cleanValues.teamID); setCurrentTeamIDValue(undefined); setCurrentTeamIDDisplayValue(\\"\\"); setTeam(cleanValues.Team); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; const [memberRecord, setMemberRecord] = React.useState(memberModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Member, idProp) : memberModelProp; setMemberRecord(record); const teamIDRecord = record ? await record.teamID : undefined; setTeamID(teamIDRecord); const TeamRecord = record ? await record.Team : undefined; setTeam(TeamRecord); }; queryData(); }, [idProp, memberModelProp]); React.useEffect(resetStateValues, [memberRecord, teamID, Team]); const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = React.useState(\\"\\"); const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); const teamIDRef = React.createRef(); const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(\\"\\"); const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); const TeamRef = React.createRef(); const getIDValue = { Team: (r) => JSON.stringify({ id: r?.id }), }; const TeamIdSet = new Set( Array.isArray(Team) ? Team.map((r) => getIDValue.Team?.(r)) : getIDValue.Team?.(Team) ); const teamRecords = useDataStoreBinding({ type: \\"collection\\", model: Team0, }).items; const getDisplayValue = { teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Team: (r) => r?.name, }; const validations = { name: [], teamID: [{ type: \\"Required\\" }], Team: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, teamID, Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save( Member.copyOf(memberRecord, (updated) => { Object.assign(updated, modelFields); if (!modelFields.Team) { updated.teamMembersId = undefined; } }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyMemberForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || memberModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || memberModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, teamID, Team, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID: value, Team, }; const result = onChange(modelFields); value = result?.teamID ?? value; } setTeamID(value); setCurrentTeamIDValue(undefined); }} currentFieldValue={currentTeamIDValue} label={\\"Team id\\"} items={teamID ? [teamID] : []} hasError={errors?.teamID?.hasError} errorMessage={errors?.teamID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentTeamIDDisplayValue( value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" ); setCurrentTeamIDValue(value); }} inputFieldRef={teamIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamIDDisplayValue} options={teamRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.teamID?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamIDValue(id); setCurrentTeamIDDisplayValue(label); runValidationTasks(\\"teamID\\", label); }} onClear={() => { setCurrentTeamIDDisplayValue(\\"\\"); }} defaultValue={teamID} onChange={(e) => { let { value } = e.target; if (errors.teamID?.hasError) { runValidationTasks(\\"teamID\\", value); } setCurrentTeamIDDisplayValue(value); setCurrentTeamIDValue(undefined); }} onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} errorMessage={errors.teamID?.errorMessage} hasError={errors.teamID?.hasError} ref={teamIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"teamID\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID, Team: value, }; const result = onChange(modelFields); value = result?.Team ?? value; } setTeam(value); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); }} currentFieldValue={currentTeamValue} label={\\"Team Label\\"} items={Team ? [Team] : []} hasError={errors?.Team?.hasError} errorMessage={errors?.Team?.errorMessage} getBadgeText={getDisplayValue.Team} setFieldValue={(model) => { setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); setCurrentTeamValue(model); }} inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team Label\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamDisplayValue} options={teamRecords .filter((r) => !TeamIdSet.has(getIDValue.Team?.(r))) .map((r) => ({ id: getIDValue.Team?.(r), label: getDisplayValue.Team?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamValue( teamRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentTeamDisplayValue(label); runValidationTasks(\\"Team\\", label); }} onClear={() => { setCurrentTeamDisplayValue(\\"\\"); }} defaultValue={Team} onChange={(e) => { let { value } = e.target; if (errors.Team?.hasError) { runValidationTasks(\\"Team\\", value); } setCurrentTeamDisplayValue(value); setCurrentTeamValue(undefined); }} onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} errorMessage={errors.Team?.errorMessage} hasError={errors.Team?.hasError} ref={TeamRef} labelHidden={true} {...getOverrideProps(overrides, \\"Team\\")} ></Autocomplete> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Member, Team as Team0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyMemberFormInputValues = { name?: string; teamID?: string; Team?: Team0; }; export declare type MyMemberFormValidationValues = { name?: ValidationFunction<string>; teamID?: ValidationFunction<string>; Team?: ValidationFunction<Team0>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyMemberFormOverridesProps = { MyMemberFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; teamID?: PrimitiveOverrideProps<AutocompleteProps>; Team?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type MyMemberFormProps = React.PropsWithChildren<{ overrides?: MyMemberFormOverridesProps | undefined | null; } & { id?: string; member?: Member; onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onSuccess?: (fields: MyMemberFormInputValues) => void; onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should generate an update form with manyToMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Tag, Post, TagPost } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function TagUpdateForm(props) { const { id: idProp, tag: tagModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { label: \\"\\", Posts: [], statuses: [], }; const [label, setLabel] = React.useState(initialValues.label); const [Posts, setPosts] = React.useState(initialValues.Posts); const [statuses, setStatuses] = React.useState(initialValues.statuses); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = tagRecord ? { ...initialValues, ...tagRecord, Posts: linkedPosts } : initialValues; setLabel(cleanValues.label); setPosts(cleanValues.Posts ?? []); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); setStatuses(cleanValues.statuses ?? []); setCurrentStatusesValue(\\"\\"); setErrors({}); }; const [tagRecord, setTagRecord] = React.useState(tagModelProp); const [linkedPosts, setLinkedPosts] = React.useState([]); const canUnlinkPosts = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Tag, idProp) : tagModelProp; setTagRecord(record); const linkedPosts = record ? await Promise.all( ( await record.Posts.toArray() ).map((r) => { return r.post; }) ) : []; setLinkedPosts(linkedPosts); }; queryData(); }, [idProp, tagModelProp]); React.useEffect(resetStateValues, [tagRecord, linkedPosts]); const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); const PostsRef = React.createRef(); const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); const statusesRef = React.createRef(); const getIDValue = { Posts: (r) => JSON.stringify({ id: r?.id }), }; const PostsIdSet = new Set( Array.isArray(Posts) ? Posts.map((r) => getIDValue.Posts?.(r)) : getIDValue.Posts?.(Posts) ); const postRecords = useDataStoreBinding({ type: \\"collection\\", model: Post, }).items; const getDisplayValue = { Posts: (r) => r?.title, statuses: (r) => { const enumDisplayValueMap = { PENDING: \\"Pending\\", POSTED: \\"Posted\\", IN_REVIEW: \\"In review\\", }; return enumDisplayValueMap[r]; }, }; const validations = { label: [], Posts: [], statuses: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { label, Posts, statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const postsToLinkMap = new Map(); const postsToUnLinkMap = new Map(); const postsMap = new Map(); const linkedPostsMap = new Map(); Posts.forEach((r) => { const count = postsMap.get(getIDValue.Posts?.(r)); const newCount = count ? count + 1 : 1; postsMap.set(getIDValue.Posts?.(r), newCount); }); linkedPosts.forEach((r) => { const count = linkedPostsMap.get(getIDValue.Posts?.(r)); const newCount = count ? count + 1 : 1; linkedPostsMap.set(getIDValue.Posts?.(r), newCount); }); linkedPostsMap.forEach((count, id) => { const newCount = postsMap.get(id); if (newCount) { const diffCount = count - newCount; if (diffCount > 0) { postsToUnLinkMap.set(id, diffCount); } } else { postsToUnLinkMap.set(id, count); } }); postsMap.forEach((count, id) => { const originalCount = linkedPostsMap.get(id); if (originalCount) { const diffCount = count - originalCount; if (diffCount > 0) { postsToLinkMap.set(id, diffCount); } } else { postsToLinkMap.set(id, count); } }); postsToUnLinkMap.forEach(async (count, id) => { const recordKeys = JSON.parse(id); const tagPostRecords = await DataStore.query(TagPost, (r) => r.and((r) => { return [r.postID.eq(recordKeys.id), r.tagID.eq(tagRecord.id)]; }) ); for (let i = 0; i < count; i++) { promises.push(DataStore.delete(tagPostRecords[i])); } }); postsToLinkMap.forEach((count, id) => { const postToLink = postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ); for (let i = count; i > 0; i--) { promises.push( DataStore.save( new TagPost({ tag: tagRecord, post: postToLink, }) ) ); } }); const modelFieldsToSave = { label: modelFields.label, statuses: modelFields.statuses, }; promises.push( DataStore.save( Tag.copyOf(tagRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"TagUpdateForm\\")} {...rest} > <TextField label=\\"Label\\" isRequired={false} isReadOnly={false} value={label} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { label: value, Posts, statuses, }; const result = onChange(modelFields); value = result?.label ?? value; } if (errors.label?.hasError) { runValidationTasks(\\"label\\", value); } setLabel(value); }} onBlur={() => runValidationTasks(\\"label\\", label)} errorMessage={errors.label?.errorMessage} hasError={errors.label?.hasError} {...getOverrideProps(overrides, \\"label\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts: values, statuses, }; const result = onChange(modelFields); values = result?.Posts ?? values; } setPosts(values); setCurrentPostsValue(undefined); setCurrentPostsDisplayValue(\\"\\"); }} currentFieldValue={currentPostsValue} label={\\"Posts\\"} items={Posts} hasError={errors?.Posts?.hasError} errorMessage={errors?.Posts?.errorMessage} getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { setCurrentPostsDisplayValue( model ? getDisplayValue.Posts(model) : \\"\\" ); setCurrentPostsValue(model); }} inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Posts\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Post\\" value={currentPostsDisplayValue} options={postRecords .filter((r) => !PostsIdSet.has(getIDValue.Posts?.(r))) .map((r) => ({ id: getIDValue.Posts?.(r), label: getDisplayValue.Posts?.(r), }))} onSelect={({ id, label }) => { setCurrentPostsValue( postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentPostsDisplayValue(label); runValidationTasks(\\"Posts\\", label); }} onClear={() => { setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Posts?.hasError) { runValidationTasks(\\"Posts\\", value); } setCurrentPostsDisplayValue(value); setCurrentPostsValue(undefined); }} onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} errorMessage={errors.Posts?.errorMessage} hasError={errors.Posts?.hasError} ref={PostsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Posts\\")} ></Autocomplete> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { label, Posts, statuses: values, }; const result = onChange(modelFields); values = result?.statuses ?? values; } setStatuses(values); setCurrentStatusesValue(\\"\\"); }} currentFieldValue={currentStatusesValue} label={\\"Statuses\\"} items={statuses} hasError={errors?.statuses?.hasError} errorMessage={errors?.statuses?.errorMessage} getBadgeText={getDisplayValue.statuses} setFieldValue={setCurrentStatusesValue} inputFieldRef={statusesRef} defaultFieldValue={\\"\\"} > <SelectField label=\\"Statuses\\" placeholder=\\"Please select an option\\" isDisabled={false} value={currentStatusesValue} onChange={(e) => { let { value } = e.target; if (errors.statuses?.hasError) { runValidationTasks(\\"statuses\\", value); } setCurrentStatusesValue(value); }} onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} errorMessage={errors.statuses?.errorMessage} hasError={errors.statuses?.hasError} ref={statusesRef} labelHidden={true} {...getOverrideProps(overrides, \\"statuses\\")} > <option children=\\"Pending\\" value=\\"PENDING\\" {...getOverrideProps(overrides, \\"statusesoption0\\")} ></option> <option children=\\"Posted\\" value=\\"POSTED\\" {...getOverrideProps(overrides, \\"statusesoption1\\")} ></option> <option children=\\"In review\\" value=\\"IN_REVIEW\\" {...getOverrideProps(overrides, \\"statusesoption2\\")} ></option> </SelectField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || tagModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || tagModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should generate an update form with manyToMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Tag, Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type TagUpdateFormInputValues = { label?: string; Posts?: Post[]; statuses?: string[]; }; export declare type TagUpdateFormValidationValues = { label?: ValidationFunction<string>; Posts?: ValidationFunction<Post>; statuses?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type TagUpdateFormOverridesProps = { TagUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; label?: PrimitiveOverrideProps<TextFieldProps>; Posts?: PrimitiveOverrideProps<AutocompleteProps>; statuses?: PrimitiveOverrideProps<SelectFieldProps>; } & EscapeHatchProps; export declare type TagUpdateFormProps = React.PropsWithChildren<{ overrides?: TagUpdateFormOverridesProps | undefined | null; } & { id?: string; tag?: Tag; onSubmit?: (fields: TagUpdateFormInputValues) => TagUpdateFormInputValues; onSuccess?: (fields: TagUpdateFormInputValues) => void; onError?: (fields: TagUpdateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: TagUpdateFormInputValues) => TagUpdateFormInputValues; onValidate?: TagUpdateFormValidationValues; } & React.CSSProperties>; export default function TagUpdateForm(props: TagUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render a create form with colliding model name 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Flex as Flex0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyFlexCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { username: \\"\\", caption: \\"\\", Customtags: [], tags: [], profile_url: \\"\\", }; const [username, setUsername] = React.useState(initialValues.username); const [caption, setCaption] = React.useState(initialValues.caption); const [Customtags, setCustomtags] = React.useState(initialValues.Customtags); const [tags, setTags] = React.useState(initialValues.tags); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setUsername(initialValues.username); setCaption(initialValues.caption); setCustomtags(initialValues.Customtags); setCurrentCustomtagsValue(\\"\\"); setTags(initialValues.tags); setCurrentTagsValue(\\"\\"); setProfile_url(initialValues.profile_url); setErrors({}); }; const [currentCustomtagsValue, setCurrentCustomtagsValue] = React.useState(\\"\\"); const CustomtagsRef = React.createRef(); const [currentTagsValue, setCurrentTagsValue] = React.useState(\\"\\"); const tagsRef = React.createRef(); const validations = { username: [ { type: \\"GreaterThanChar\\", numValues: [2], validationMessage: \\"needs to be of length 2\\", }, ], caption: [], Customtags: [], tags: [], profile_url: [{ type: \\"URL\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { username, caption, Customtags, tags, profile_url, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { username: modelFields.username, caption: modelFields.caption, tags: modelFields.tags, profile_url: modelFields.profile_url, }; await DataStore.save(new Flex0(modelFieldsToSave)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyFlexCreateForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <Grid columnGap=\\"inherit\\" rowGap=\\"inherit\\" templateColumns=\\"repeat(2, auto)\\" {...getOverrideProps(overrides, \\"RowGrid0\\")} > <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} placeholder=\\"john\\" value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username: value, caption, Customtags, tags, profile_url, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} placeholder=\\"i love code\\" value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption: value, Customtags, tags, profile_url, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> </Grid> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags: values, tags, profile_url, }; const result = onChange(modelFields); values = result?.Customtags ?? values; } setCustomtags(values); setCurrentCustomtagsValue(\\"\\"); }} currentFieldValue={currentCustomtagsValue} label={\\"Tags\\"} items={Customtags} hasError={errors?.Customtags?.hasError} errorMessage={errors?.Customtags?.errorMessage} setFieldValue={setCurrentCustomtagsValue} inputFieldRef={CustomtagsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Tags\\" placeholder=\\"goals\\" value={currentCustomtagsValue} onChange={(e) => { let { value } = e.target; if (errors.Customtags?.hasError) { runValidationTasks(\\"Customtags\\", value); } setCurrentCustomtagsValue(value); }} onBlur={() => runValidationTasks(\\"Customtags\\", currentCustomtagsValue) } errorMessage={errors.Customtags?.errorMessage} hasError={errors.Customtags?.hasError} ref={CustomtagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Customtags\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags, tags: values, profile_url, }; const result = onChange(modelFields); values = result?.tags ?? values; } setTags(values); setCurrentTagsValue(\\"\\"); }} currentFieldValue={currentTagsValue} label={\\"Tags\\"} items={tags} hasError={errors?.tags?.hasError} errorMessage={errors?.tags?.errorMessage} setFieldValue={setCurrentTagsValue} inputFieldRef={tagsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Tags\\" isRequired={false} isReadOnly={false} value={currentTagsValue} onChange={(e) => { let { value } = e.target; if (errors.tags?.hasError) { runValidationTasks(\\"tags\\", value); } setCurrentTagsValue(value); }} onBlur={() => runValidationTasks(\\"tags\\", currentTagsValue)} errorMessage={errors.tags?.errorMessage} hasError={errors.tags?.hasError} ref={tagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"tags\\")} ></TextField> </ArrayField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, tags, profile_url: value, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render a create form with colliding model name 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyFlexCreateFormInputValues = { username?: string; caption?: string; Customtags?: string[]; tags?: string[]; profile_url?: string; }; export declare type MyFlexCreateFormValidationValues = { username?: ValidationFunction<string>; caption?: ValidationFunction<string>; Customtags?: ValidationFunction<string>; tags?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyFlexCreateFormOverridesProps = { MyFlexCreateFormGrid?: PrimitiveOverrideProps<GridProps>; RowGrid0?: PrimitiveOverrideProps<GridProps>; username?: PrimitiveOverrideProps<TextFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; Customtags?: PrimitiveOverrideProps<TextFieldProps>; tags?: PrimitiveOverrideProps<TextFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type MyFlexCreateFormProps = React.PropsWithChildren<{ overrides?: MyFlexCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyFlexCreateFormInputValues) => MyFlexCreateFormInputValues; onSuccess?: (fields: MyFlexCreateFormInputValues) => void; onError?: (fields: MyFlexCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyFlexCreateFormInputValues) => MyFlexCreateFormInputValues; onValidate?: MyFlexCreateFormValidationValues; } & React.CSSProperties>; export default function MyFlexCreateForm(props: MyFlexCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SwitchField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Blog } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function BlogCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { title: \\"\\", content: \\"\\", switch: false, published: \\"\\", editedAt: [], }; const [title, setTitle] = React.useState(initialValues.title); const [content, setContent] = React.useState(initialValues.content); const [switch1, setSwitch1] = React.useState(initialValues.switch); const [published, setPublished] = React.useState(initialValues.published); const [editedAt, setEditedAt] = React.useState(initialValues.editedAt); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setTitle(initialValues.title); setContent(initialValues.content); setSwitch1(initialValues.switch); setPublished(initialValues.published); setEditedAt(initialValues.editedAt); setCurrentEditedAtValue(\\"\\"); setErrors({}); }; const [currentEditedAtValue, setCurrentEditedAtValue] = React.useState(\\"\\"); const editedAtRef = React.createRef(); const validations = { title: [], content: [], switch: [], published: [], editedAt: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const convertToLocal = (date) => { const df = new Intl.DateTimeFormat(\\"default\\", { year: \\"numeric\\", month: \\"2-digit\\", day: \\"2-digit\\", hour: \\"2-digit\\", minute: \\"2-digit\\", calendar: \\"iso8601\\", numberingSystem: \\"latn\\", hourCycle: \\"h23\\", }); const parts = df.formatToParts(date).reduce((acc, part) => { acc[part.type] = part.value; return acc; }, {}); return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { title, content, switch: switch1, published, editedAt, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Blog(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"BlogCreateForm\\")} {...rest} > <TextField label=\\"Title\\" isRequired={false} isReadOnly={false} value={title} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { title: value, content, switch: switch1, published, editedAt, }; const result = onChange(modelFields); value = result?.title ?? value; } if (errors.title?.hasError) { runValidationTasks(\\"title\\", value); } setTitle(value); }} onBlur={() => runValidationTasks(\\"title\\", title)} errorMessage={errors.title?.errorMessage} hasError={errors.title?.hasError} {...getOverrideProps(overrides, \\"title\\")} ></TextField> <TextField label=\\"Content\\" isRequired={false} isReadOnly={false} value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { title, content: value, switch: switch1, published, editedAt, }; const result = onChange(modelFields); value = result?.content ?? value; } if (errors.content?.hasError) { runValidationTasks(\\"content\\", value); } setContent(value); }} onBlur={() => runValidationTasks(\\"content\\", content)} errorMessage={errors.content?.errorMessage} hasError={errors.content?.hasError} {...getOverrideProps(overrides, \\"content\\")} ></TextField> <SwitchField label=\\"Switch\\" defaultChecked={false} isDisabled={false} isChecked={switch1} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { title, content, switch: value, published, editedAt, }; const result = onChange(modelFields); value = result?.switch ?? value; } if (errors.switch?.hasError) { runValidationTasks(\\"switch\\", value); } setSwitch1(value); }} onBlur={() => runValidationTasks(\\"switch\\", switch1)} errorMessage={errors.switch?.errorMessage} hasError={errors.switch?.hasError} {...getOverrideProps(overrides, \\"switch\\")} ></SwitchField> <TextField label=\\"Published\\" isRequired={false} isReadOnly={false} type=\\"datetime-local\\" value={published && convertToLocal(new Date(published))} onChange={(e) => { let value = e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); if (onChange) { const modelFields = { title, content, switch: switch1, published: value, editedAt, }; const result = onChange(modelFields); value = result?.published ?? value; } if (errors.published?.hasError) { runValidationTasks(\\"published\\", value); } setPublished(value); }} onBlur={() => runValidationTasks(\\"published\\", published)} errorMessage={errors.published?.errorMessage} hasError={errors.published?.hasError} {...getOverrideProps(overrides, \\"published\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { title, content, switch: switch1, published, editedAt: values, }; const result = onChange(modelFields); values = result?.editedAt ?? values; } setEditedAt(values); setCurrentEditedAtValue(\\"\\"); }} currentFieldValue={currentEditedAtValue} label={\\"Edited at\\"} items={editedAt} hasError={errors?.editedAt?.hasError} errorMessage={errors?.editedAt?.errorMessage} setFieldValue={setCurrentEditedAtValue} inputFieldRef={editedAtRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Edited at\\" isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={currentEditedAtValue} onChange={(e) => { let value = isNaN(parseInt(e.target.value)) ? e.target.value : parseInt(e.target.value); if (errors.editedAt?.hasError) { runValidationTasks(\\"editedAt\\", value); } setCurrentEditedAtValue(value); }} onBlur={() => runValidationTasks(\\"editedAt\\", currentEditedAtValue)} errorMessage={errors.editedAt?.errorMessage} hasError={errors.editedAt?.hasError} ref={editedAtRef} labelHidden={true} {...getOverrideProps(overrides, \\"editedAt\\")} ></TextField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 2`] = ` "import * as React from \\"react\\"; import { GridProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type BlogCreateFormInputValues = { title?: string; content?: string; switch?: boolean; published?: string; editedAt?: number[]; }; export declare type BlogCreateFormValidationValues = { title?: ValidationFunction<string>; content?: ValidationFunction<string>; switch?: ValidationFunction<boolean>; published?: ValidationFunction<string>; editedAt?: ValidationFunction<number>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type BlogCreateFormOverridesProps = { BlogCreateFormGrid?: PrimitiveOverrideProps<GridProps>; title?: PrimitiveOverrideProps<TextFieldProps>; content?: PrimitiveOverrideProps<TextFieldProps>; switch?: PrimitiveOverrideProps<SwitchFieldProps>; published?: PrimitiveOverrideProps<TextFieldProps>; editedAt?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type BlogCreateFormProps = React.PropsWithChildren<{ overrides?: BlogCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; onSuccess?: (fields: BlogCreateFormInputValues) => void; onError?: (fields: BlogCreateFormInputValues, errorMessage: string) => void; onChange?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; onValidate?: BlogCreateFormValidationValues; } & React.CSSProperties>; export default function BlogCreateForm(props: BlogCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render a form with multiple date types on create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, CheckboxField, Divider, Flex, Grid, Icon, Radio, RadioGroupField, ScrollView, Text, TextAreaField, TextField, ToggleButton, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { InputGallery } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function InputGalleryCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { num: \\"\\", rootbeer: \\"\\", attend: undefined, maybeSlide: false, maybeCheck: false, arrayTypeField: [], jsonArray: [], jsonField: \\"\\", timestamp: \\"\\", ippy: \\"\\", timeisnow: \\"\\", }; const [num, setNum] = React.useState(initialValues.num); const [rootbeer, setRootbeer] = React.useState(initialValues.rootbeer); const [attend, setAttend] = React.useState(initialValues.attend); const [maybeSlide, setMaybeSlide] = React.useState(initialValues.maybeSlide); const [maybeCheck, setMaybeCheck] = React.useState(initialValues.maybeCheck); const [arrayTypeField, setArrayTypeField] = React.useState( initialValues.arrayTypeField ); const [jsonArray, setJsonArray] = React.useState(initialValues.jsonArray); const [jsonField, setJsonField] = React.useState(initialValues.jsonField); const [timestamp, setTimestamp] = React.useState(initialValues.timestamp); const [ippy, setIppy] = React.useState(initialValues.ippy); const [timeisnow, setTimeisnow] = React.useState(initialValues.timeisnow); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setNum(initialValues.num); setRootbeer(initialValues.rootbeer); setAttend(initialValues.attend); setMaybeSlide(initialValues.maybeSlide); setMaybeCheck(initialValues.maybeCheck); setArrayTypeField(initialValues.arrayTypeField); setCurrentArrayTypeFieldValue(\\"\\"); setJsonArray(initialValues.jsonArray); setCurrentJsonArrayValue(\\"\\"); setJsonField(initialValues.jsonField); setTimestamp(initialValues.timestamp); setIppy(initialValues.ippy); setTimeisnow(initialValues.timeisnow); setErrors({}); }; const [currentArrayTypeFieldValue, setCurrentArrayTypeFieldValue] = React.useState(\\"\\"); const arrayTypeFieldRef = React.createRef(); const [currentJsonArrayValue, setCurrentJsonArrayValue] = React.useState(\\"\\"); const jsonArrayRef = React.createRef(); const validations = { num: [], rootbeer: [], attend: [{ type: \\"Required\\" }], maybeSlide: [], maybeCheck: [], arrayTypeField: [], jsonArray: [{ type: \\"JSON\\" }], jsonField: [{ type: \\"JSON\\" }], timestamp: [], ippy: [{ type: \\"IpAddress\\" }], timeisnow: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; const convertTimeStampToDate = (ts) => { if (Math.abs(Date.now() - ts) < Math.abs(Date.now() - ts * 1000)) { return new Date(ts); } return new Date(ts * 1000); }; const convertToLocal = (date) => { const df = new Intl.DateTimeFormat(\\"default\\", { year: \\"numeric\\", month: \\"2-digit\\", day: \\"2-digit\\", hour: \\"2-digit\\", minute: \\"2-digit\\", calendar: \\"iso8601\\", numberingSystem: \\"latn\\", hourCycle: \\"h23\\", }); const parts = df.formatToParts(date).reduce((acc, part) => { acc[part.type] = part.value; return acc; }, {}); return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new InputGallery(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"InputGalleryCreateForm\\")} {...rest} > <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Num</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={num} onChange={(e) => { let value = isNaN(parseInt(e.target.value)) ? e.target.value : parseInt(e.target.value); if (onChange) { const modelFields = { num: value, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.num ?? value; } if (errors.num?.hasError) { runValidationTasks(\\"num\\", value); } setNum(value); }} onBlur={() => runValidationTasks(\\"num\\", num)} errorMessage={errors.num?.errorMessage} hasError={errors.num?.hasError} {...getOverrideProps(overrides, \\"num\\")} ></TextField> <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Rootbeer</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={rootbeer} onChange={(e) => { let value = isNaN(parseFloat(e.target.value)) ? e.target.value : parseFloat(e.target.value); if (onChange) { const modelFields = { num, rootbeer: value, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.rootbeer ?? value; } if (errors.rootbeer?.hasError) { runValidationTasks(\\"rootbeer\\", value); } setRootbeer(value); }} onBlur={() => runValidationTasks(\\"rootbeer\\", rootbeer)} errorMessage={errors.rootbeer?.errorMessage} hasError={errors.rootbeer?.hasError} {...getOverrideProps(overrides, \\"rootbeer\\")} ></TextField> <RadioGroupField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Attend</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } name=\\"attend\\" isReadOnly={false} isRequired=\\"false\\" onChange={(e) => { let value = e.target.value === \\"true\\"; if (onChange) { const modelFields = { num, rootbeer, attend: value, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.attend ?? value; } if (errors.attend?.hasError) { runValidationTasks(\\"attend\\", value); } setAttend(value); }} onBlur={() => runValidationTasks(\\"attend\\", attend)} errorMessage={errors.attend?.errorMessage} hasError={errors.attend?.hasError} {...getOverrideProps(overrides, \\"attend\\")} > <Radio children=\\"Yes\\" value=\\"true\\" {...getOverrideProps(overrides, \\"attendRadio0\\")} ></Radio> <Radio children=\\"No\\" value=\\"false\\" {...getOverrideProps(overrides, \\"attendRadio1\\")} ></Radio> </RadioGroupField> <ToggleButton children={ <span style={{ display: \\"inline-flex\\" }}> <span>Maybe Slide</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isDisabled={false} defaultPressed={false} isPressed={maybeSlide} onChange={(e) => { let value = !maybeSlide; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide: value, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.maybeSlide ?? value; } if (errors.maybeSlide?.hasError) { runValidationTasks(\\"maybeSlide\\", value); } setMaybeSlide(value); }} onBlur={() => runValidationTasks(\\"maybeSlide\\", maybeSlide)} errorMessage={errors.maybeSlide?.errorMessage} hasError={errors.maybeSlide?.hasError} {...getOverrideProps(overrides, \\"maybeSlide\\")} ></ToggleButton> <CheckboxField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Maybe check</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } name=\\"maybeCheck\\" value=\\"maybeCheck\\" isDisabled={false} checked={maybeCheck} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck: value, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.maybeCheck ?? value; } if (errors.maybeCheck?.hasError) { runValidationTasks(\\"maybeCheck\\", value); } setMaybeCheck(value); }} onBlur={() => runValidationTasks(\\"maybeCheck\\", maybeCheck)} errorMessage={errors.maybeCheck?.errorMessage} hasError={errors.maybeCheck?.hasError} {...getOverrideProps(overrides, \\"maybeCheck\\")} ></CheckboxField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField: values, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); values = result?.arrayTypeField ?? values; } setArrayTypeField(values); setCurrentArrayTypeFieldValue(\\"\\"); }} currentFieldValue={currentArrayTypeFieldValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>Array type field</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } items={arrayTypeField} hasError={errors?.arrayTypeField?.hasError} errorMessage={errors?.arrayTypeField?.errorMessage} setFieldValue={setCurrentArrayTypeFieldValue} inputFieldRef={arrayTypeFieldRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Array type field\\" isRequired={false} isReadOnly={false} value={currentArrayTypeFieldValue} onChange={(e) => { let { value } = e.target; if (errors.arrayTypeField?.hasError) { runValidationTasks(\\"arrayTypeField\\", value); } setCurrentArrayTypeFieldValue(value); }} onBlur={() => runValidationTasks(\\"arrayTypeField\\", currentArrayTypeFieldValue) } errorMessage={errors.arrayTypeField?.errorMessage} hasError={errors.arrayTypeField?.hasError} ref={arrayTypeFieldRef} labelHidden={true} {...getOverrideProps(overrides, \\"arrayTypeField\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray: values, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); values = result?.jsonArray ?? values; } setJsonArray(values); setCurrentJsonArrayValue(\\"\\"); }} currentFieldValue={currentJsonArrayValue} label={ <span style={{ display: \\"inline-flex\\" }}> <span>Json array</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } items={jsonArray} hasError={errors?.jsonArray?.hasError} errorMessage={errors?.jsonArray?.errorMessage} setFieldValue={setCurrentJsonArrayValue} inputFieldRef={jsonArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Json array\\" isRequired={false} isReadOnly={false} value={currentJsonArrayValue} onChange={(e) => { let { value } = e.target; if (errors.jsonArray?.hasError) { runValidationTasks(\\"jsonArray\\", value); } setCurrentJsonArrayValue(value); }} onBlur={() => runValidationTasks(\\"jsonArray\\", currentJsonArrayValue)} errorMessage={errors.jsonArray?.errorMessage} hasError={errors.jsonArray?.hasError} ref={jsonArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"jsonArray\\")} ></TextAreaField> </ArrayField> <TextAreaField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Json field</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField: value, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.jsonField ?? value; } if (errors.jsonField?.hasError) { runValidationTasks(\\"jsonField\\", value); } setJsonField(value); }} onBlur={() => runValidationTasks(\\"jsonField\\", jsonField)} errorMessage={errors.jsonField?.errorMessage} hasError={errors.jsonField?.hasError} {...getOverrideProps(overrides, \\"jsonField\\")} ></TextAreaField> <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Timestamp</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} type=\\"datetime-local\\" value={timestamp && convertToLocal(convertTimeStampToDate(timestamp))} onChange={(e) => { let value = e.target.value === \\"\\" ? \\"\\" : Number(new Date(e.target.value)); if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp: value, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.timestamp ?? value; } if (errors.timestamp?.hasError) { runValidationTasks(\\"timestamp\\", value); } setTimestamp(value); }} onBlur={() => runValidationTasks(\\"timestamp\\", timestamp)} errorMessage={errors.timestamp?.errorMessage} hasError={errors.timestamp?.hasError} {...getOverrideProps(overrides, \\"timestamp\\")} ></TextField> <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Ippy</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} value={ippy} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy: value, timeisnow, }; const result = onChange(modelFields); value = result?.ippy ?? value; } if (errors.ippy?.hasError) { runValidationTasks(\\"ippy\\", value); } setIppy(value); }} onBlur={() => runValidationTasks(\\"ippy\\", ippy)} errorMessage={errors.ippy?.errorMessage} hasError={errors.ippy?.hasError} {...getOverrideProps(overrides, \\"ippy\\")} ></TextField> <TextField label={ <span style={{ display: \\"inline-flex\\" }}> <span>Timeisnow</span> <span style={{ whiteSpace: \\"pre\\", fontStyle: \\"italic\\" }}> {\\" \\"} - optional </span> </span> } isRequired={false} isReadOnly={false} type=\\"time\\" value={timeisnow} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow: value, }; const result = onChange(modelFields); value = result?.timeisnow ?? value; } if (errors.timeisnow?.hasError) { runValidationTasks(\\"timeisnow\\", value); } setTimeisnow(value); }} onBlur={() => runValidationTasks(\\"timeisnow\\", timeisnow)} errorMessage={errors.timeisnow?.errorMessage} hasError={errors.timeisnow?.hasError} {...getOverrideProps(overrides, \\"timeisnow\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render a form with multiple date types on create form 2`] = ` "import * as React from \\"react\\"; import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextAreaFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type InputGalleryCreateFormInputValues = { num?: number; rootbeer?: number; attend?: boolean; maybeSlide?: boolean; maybeCheck?: boolean; arrayTypeField?: string[]; jsonArray?: string[]; jsonField?: string; timestamp?: number; ippy?: string; timeisnow?: string; }; export declare type InputGalleryCreateFormValidationValues = { num?: ValidationFunction<number>; rootbeer?: ValidationFunction<number>; attend?: ValidationFunction<boolean>; maybeSlide?: ValidationFunction<boolean>; maybeCheck?: ValidationFunction<boolean>; arrayTypeField?: ValidationFunction<string>; jsonArray?: ValidationFunction<string>; jsonField?: ValidationFunction<string>; timestamp?: ValidationFunction<number>; ippy?: ValidationFunction<string>; timeisnow?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type InputGalleryCreateFormOverridesProps = { InputGalleryCreateFormGrid?: PrimitiveOverrideProps<GridProps>; num?: PrimitiveOverrideProps<TextFieldProps>; rootbeer?: PrimitiveOverrideProps<TextFieldProps>; attend?: PrimitiveOverrideProps<RadioGroupFieldProps>; maybeSlide?: PrimitiveOverrideProps<ToggleButtonProps>; maybeCheck?: PrimitiveOverrideProps<CheckboxFieldProps>; arrayTypeField?: PrimitiveOverrideProps<TextFieldProps>; jsonArray?: PrimitiveOverrideProps<TextAreaFieldProps>; jsonField?: PrimitiveOverrideProps<TextAreaFieldProps>; timestamp?: PrimitiveOverrideProps<TextFieldProps>; ippy?: PrimitiveOverrideProps<TextFieldProps>; timeisnow?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type InputGalleryCreateFormProps = React.PropsWithChildren<{ overrides?: InputGalleryCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; onSuccess?: (fields: InputGalleryCreateFormInputValues) => void; onError?: (fields: InputGalleryCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; onValidate?: InputGalleryCreateFormValidationValues; } & React.CSSProperties>; export default function InputGalleryCreateForm(props: InputGalleryCreateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render a form with multiple date types on update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, CheckboxField, Divider, Flex, Grid, Icon, Radio, RadioGroupField, ScrollView, Text, TextAreaField, TextField, ToggleButton, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { InputGallery } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function InputGalleryUpdateForm(props) { const { id: idProp, inputGallery: inputGalleryModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { num: \\"\\", rootbeer: \\"\\", attend: undefined, maybeSlide: false, maybeCheck: false, arrayTypeField: [], jsonArray: [], jsonField: \\"\\", timestamp: \\"\\", ippy: \\"\\", timeisnow: \\"\\", }; const [num, setNum] = React.useState(initialValues.num); const [rootbeer, setRootbeer] = React.useState(initialValues.rootbeer); const [attend, setAttend] = React.useState(initialValues.attend); const [maybeSlide, setMaybeSlide] = React.useState(initialValues.maybeSlide); const [maybeCheck, setMaybeCheck] = React.useState(initialValues.maybeCheck); const [arrayTypeField, setArrayTypeField] = React.useState( initialValues.arrayTypeField ); const [jsonArray, setJsonArray] = React.useState(initialValues.jsonArray); const [jsonField, setJsonField] = React.useState(initialValues.jsonField); const [timestamp, setTimestamp] = React.useState(initialValues.timestamp); const [ippy, setIppy] = React.useState(initialValues.ippy); const [timeisnow, setTimeisnow] = React.useState(initialValues.timeisnow); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = inputGalleryRecord ? { ...initialValues, ...inputGalleryRecord } : initialValues; setNum(cleanValues.num); setRootbeer(cleanValues.rootbeer); setAttend(cleanValues.attend); setMaybeSlide(cleanValues.maybeSlide); setMaybeCheck(cleanValues.maybeCheck); setArrayTypeField(cleanValues.arrayTypeField ?? []); setCurrentArrayTypeFieldValue(\\"\\"); setJsonArray(cleanValues.jsonArray ?? []); setCurrentJsonArrayValue(\\"\\"); setJsonField( typeof cleanValues.jsonField === \\"string\\" || cleanValues.jsonField === null ? cleanValues.jsonField : JSON.stringify(cleanValues.jsonField) ); setTimestamp(cleanValues.timestamp); setIppy(cleanValues.ippy); setTimeisnow(cleanValues.timeisnow); setErrors({}); }; const [inputGalleryRecord, setInputGalleryRecord] = React.useState( inputGalleryModelProp ); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(InputGallery, idProp) : inputGalleryModelProp; setInputGalleryRecord(record); }; queryData(); }, [idProp, inputGalleryModelProp]); React.useEffect(resetStateValues, [inputGalleryRecord]); const [currentArrayTypeFieldValue, setCurrentArrayTypeFieldValue] = React.useState(\\"\\"); const arrayTypeFieldRef = React.createRef(); const [currentJsonArrayValue, setCurrentJsonArrayValue] = React.useState(\\"\\"); const jsonArrayRef = React.createRef(); const validations = { num: [], rootbeer: [], attend: [{ type: \\"Required\\" }], maybeSlide: [], maybeCheck: [], arrayTypeField: [], jsonArray: [{ type: \\"JSON\\" }], jsonField: [{ type: \\"JSON\\" }], timestamp: [], ippy: [{ type: \\"IpAddress\\" }], timeisnow: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save( InputGallery.copyOf(inputGalleryRecord, (updated) => { Object.assign(updated, modelFields); }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"InputGalleryUpdateForm\\")} {...rest} > <TextField label=\\"Num\\" isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={num} onChange={(e) => { let value = isNaN(parseInt(e.target.value)) ? e.target.value : parseInt(e.target.value); if (onChange) { const modelFields = { num: value, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.num ?? value; } if (errors.num?.hasError) { runValidationTasks(\\"num\\", value); } setNum(value); }} onBlur={() => runValidationTasks(\\"num\\", num)} errorMessage={errors.num?.errorMessage} hasError={errors.num?.hasError} {...getOverrideProps(overrides, \\"num\\")} ></TextField> <TextField label=\\"Rootbeer\\" isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={rootbeer} onChange={(e) => { let value = isNaN(parseFloat(e.target.value)) ? e.target.value : parseFloat(e.target.value); if (onChange) { const modelFields = { num, rootbeer: value, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.rootbeer ?? value; } if (errors.rootbeer?.hasError) { runValidationTasks(\\"rootbeer\\", value); } setRootbeer(value); }} onBlur={() => runValidationTasks(\\"rootbeer\\", rootbeer)} errorMessage={errors.rootbeer?.errorMessage} hasError={errors.rootbeer?.hasError} {...getOverrideProps(overrides, \\"rootbeer\\")} ></TextField> <RadioGroupField label=\\"Attend\\" name=\\"attend\\" isReadOnly={false} isRequired=\\"false\\" defaultValue={attend} onChange={(e) => { let value = e.target.value === \\"true\\"; if (onChange) { const modelFields = { num, rootbeer, attend: value, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.attend ?? value; } if (errors.attend?.hasError) { runValidationTasks(\\"attend\\", value); } setAttend(value); }} onBlur={() => runValidationTasks(\\"attend\\", attend)} errorMessage={errors.attend?.errorMessage} hasError={errors.attend?.hasError} {...getOverrideProps(overrides, \\"attend\\")} > <Radio children=\\"Yes\\" value=\\"true\\" {...getOverrideProps(overrides, \\"attendRadio0\\")} ></Radio> <Radio children=\\"No\\" value=\\"false\\" {...getOverrideProps(overrides, \\"attendRadio1\\")} ></Radio> </RadioGroupField> <ToggleButton children=\\"Maybe slide\\" isDisabled={false} defaultPressed={false} isPressed={maybeSlide} onChange={(e) => { let value = !maybeSlide; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide: value, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.maybeSlide ?? value; } if (errors.maybeSlide?.hasError) { runValidationTasks(\\"maybeSlide\\", value); } setMaybeSlide(value); }} onBlur={() => runValidationTasks(\\"maybeSlide\\", maybeSlide)} errorMessage={errors.maybeSlide?.errorMessage} hasError={errors.maybeSlide?.hasError} {...getOverrideProps(overrides, \\"maybeSlide\\")} ></ToggleButton> <CheckboxField label=\\"Maybe check\\" name=\\"maybeCheck\\" value=\\"maybeCheck\\" isDisabled={false} checked={maybeCheck} defaultValue={maybeCheck} onChange={(e) => { let value = e.target.checked; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck: value, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.maybeCheck ?? value; } if (errors.maybeCheck?.hasError) { runValidationTasks(\\"maybeCheck\\", value); } setMaybeCheck(value); }} onBlur={() => runValidationTasks(\\"maybeCheck\\", maybeCheck)} errorMessage={errors.maybeCheck?.errorMessage} hasError={errors.maybeCheck?.hasError} {...getOverrideProps(overrides, \\"maybeCheck\\")} ></CheckboxField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField: values, jsonArray, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); values = result?.arrayTypeField ?? values; } setArrayTypeField(values); setCurrentArrayTypeFieldValue(\\"\\"); }} currentFieldValue={currentArrayTypeFieldValue} label={\\"Array type field\\"} items={arrayTypeField} hasError={errors?.arrayTypeField?.hasError} errorMessage={errors?.arrayTypeField?.errorMessage} setFieldValue={setCurrentArrayTypeFieldValue} inputFieldRef={arrayTypeFieldRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Array type field\\" isRequired={false} isReadOnly={false} value={currentArrayTypeFieldValue} onChange={(e) => { let { value } = e.target; if (errors.arrayTypeField?.hasError) { runValidationTasks(\\"arrayTypeField\\", value); } setCurrentArrayTypeFieldValue(value); }} onBlur={() => runValidationTasks(\\"arrayTypeField\\", currentArrayTypeFieldValue) } errorMessage={errors.arrayTypeField?.errorMessage} hasError={errors.arrayTypeField?.hasError} ref={arrayTypeFieldRef} labelHidden={true} {...getOverrideProps(overrides, \\"arrayTypeField\\")} ></TextField> </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray: values, jsonField, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); values = result?.jsonArray ?? values; } setJsonArray(values); setCurrentJsonArrayValue(\\"\\"); }} currentFieldValue={currentJsonArrayValue} label={\\"Json array\\"} items={jsonArray} hasError={errors?.jsonArray?.hasError} errorMessage={errors?.jsonArray?.errorMessage} setFieldValue={setCurrentJsonArrayValue} inputFieldRef={jsonArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Json array\\" isRequired={false} isReadOnly={false} value={currentJsonArrayValue} onChange={(e) => { let { value } = e.target; if (errors.jsonArray?.hasError) { runValidationTasks(\\"jsonArray\\", value); } setCurrentJsonArrayValue(value); }} onBlur={() => runValidationTasks(\\"jsonArray\\", currentJsonArrayValue)} errorMessage={errors.jsonArray?.errorMessage} hasError={errors.jsonArray?.hasError} ref={jsonArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"jsonArray\\")} ></TextAreaField> </ArrayField> <TextAreaField label=\\"Json field\\" isRequired={false} isReadOnly={false} value={jsonField} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField: value, timestamp, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.jsonField ?? value; } if (errors.jsonField?.hasError) { runValidationTasks(\\"jsonField\\", value); } setJsonField(value); }} onBlur={() => runValidationTasks(\\"jsonField\\", jsonField)} errorMessage={errors.jsonField?.errorMessage} hasError={errors.jsonField?.hasError} {...getOverrideProps(overrides, \\"jsonField\\")} ></TextAreaField> <TextField label=\\"Timestamp\\" isRequired={false} isReadOnly={false} type=\\"number\\" step=\\"any\\" value={timestamp} onChange={(e) => { let value = isNaN(parseInt(e.target.value)) ? e.target.value : parseInt(e.target.value); if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp: value, ippy, timeisnow, }; const result = onChange(modelFields); value = result?.timestamp ?? value; } if (errors.timestamp?.hasError) { runValidationTasks(\\"timestamp\\", value); } setTimestamp(value); }} onBlur={() => runValidationTasks(\\"timestamp\\", timestamp)} errorMessage={errors.timestamp?.errorMessage} hasError={errors.timestamp?.hasError} {...getOverrideProps(overrides, \\"timestamp\\")} ></TextField> <TextField label=\\"Ippy\\" isRequired={false} isReadOnly={false} value={ippy} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy: value, timeisnow, }; const result = onChange(modelFields); value = result?.ippy ?? value; } if (errors.ippy?.hasError) { runValidationTasks(\\"ippy\\", value); } setIppy(value); }} onBlur={() => runValidationTasks(\\"ippy\\", ippy)} errorMessage={errors.ippy?.errorMessage} hasError={errors.ippy?.hasError} {...getOverrideProps(overrides, \\"ippy\\")} ></TextField> <TextField label=\\"Timeisnow\\" isRequired={false} isReadOnly={false} type=\\"time\\" value={timeisnow} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { num, rootbeer, attend, maybeSlide, maybeCheck, arrayTypeField, jsonArray, jsonField, timestamp, ippy, timeisnow: value, }; const result = onChange(modelFields); value = result?.timeisnow ?? value; } if (errors.timeisnow?.hasError) { runValidationTasks(\\"timeisnow\\", value); } setTimeisnow(value); }} onBlur={() => runValidationTasks(\\"timeisnow\\", timeisnow)} errorMessage={errors.timeisnow?.errorMessage} hasError={errors.timeisnow?.hasError} {...getOverrideProps(overrides, \\"timeisnow\\")} ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || inputGalleryModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || inputGalleryModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render a form with multiple date types on update form 2`] = ` "import * as React from \\"react\\"; import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextAreaFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { InputGallery } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type InputGalleryUpdateFormInputValues = { num?: number; rootbeer?: number; attend?: boolean; maybeSlide?: boolean; maybeCheck?: boolean; arrayTypeField?: string[]; jsonArray?: string[]; jsonField?: string; timestamp?: number; ippy?: string; timeisnow?: string; }; export declare type InputGalleryUpdateFormValidationValues = { num?: ValidationFunction<number>; rootbeer?: ValidationFunction<number>; attend?: ValidationFunction<boolean>; maybeSlide?: ValidationFunction<boolean>; maybeCheck?: ValidationFunction<boolean>; arrayTypeField?: ValidationFunction<string>; jsonArray?: ValidationFunction<string>; jsonField?: ValidationFunction<string>; timestamp?: ValidationFunction<number>; ippy?: ValidationFunction<string>; timeisnow?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type InputGalleryUpdateFormOverridesProps = { InputGalleryUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; num?: PrimitiveOverrideProps<TextFieldProps>; rootbeer?: PrimitiveOverrideProps<TextFieldProps>; attend?: PrimitiveOverrideProps<RadioGroupFieldProps>; maybeSlide?: PrimitiveOverrideProps<ToggleButtonProps>; maybeCheck?: PrimitiveOverrideProps<CheckboxFieldProps>; arrayTypeField?: PrimitiveOverrideProps<TextFieldProps>; jsonArray?: PrimitiveOverrideProps<TextAreaFieldProps>; jsonField?: PrimitiveOverrideProps<TextAreaFieldProps>; timestamp?: PrimitiveOverrideProps<TextFieldProps>; ippy?: PrimitiveOverrideProps<TextFieldProps>; timeisnow?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type InputGalleryUpdateFormProps = React.PropsWithChildren<{ overrides?: InputGalleryUpdateFormOverridesProps | undefined | null; } & { id?: string; inputGallery?: InputGallery; onSubmit?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; onSuccess?: (fields: InputGalleryUpdateFormInputValues) => void; onError?: (fields: InputGalleryUpdateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; onValidate?: InputGalleryUpdateFormValidationValues; } & React.CSSProperties>; export default function InputGalleryUpdateForm(props: InputGalleryUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render a update form with colliding model name 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Flex as Flex0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyFlexUpdateForm(props) { const { id: idProp, flex: flexModelProp, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { username: \\"\\", caption: \\"\\", Customtags: [], Autocomplete: undefined, tags: [], profile_url: \\"\\", }; const [username, setUsername] = React.useState(initialValues.username); const [caption, setCaption] = React.useState(initialValues.caption); const [Customtags, setCustomtags] = React.useState(initialValues.Customtags); const [Autocomplete, setAutocomplete] = React.useState( initialValues.Autocomplete ); const [tags, setTags] = React.useState(initialValues.tags); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = flexRecord ? { ...initialValues, ...flexRecord } : initialValues; setUsername(cleanValues.username); setCaption(cleanValues.caption); setCustomtags(cleanValues.Customtags ?? []); setCurrentCustomtagsValue(\\"\\"); setAutocomplete(cleanValues.Autocomplete); setTags(cleanValues.tags ?? []); setCurrentTagsValue(\\"\\"); setProfile_url(cleanValues.profile_url); setErrors({}); }; const [flexRecord, setFlexRecord] = React.useState(flexModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Flex0, idProp) : flexModelProp; setFlexRecord(record); }; queryData(); }, [idProp, flexModelProp]); React.useEffect(resetStateValues, [flexRecord]); const [currentCustomtagsValue, setCurrentCustomtagsValue] = React.useState(\\"\\"); const CustomtagsRef = React.createRef(); const [currentTagsValue, setCurrentTagsValue] = React.useState(\\"\\"); const tagsRef = React.createRef(); const validations = { username: [ { type: \\"GreaterThanChar\\", numValues: [2], validationMessage: \\"needs to be of length 2\\", }, ], caption: [], Customtags: [], Autocomplete: [], tags: [], profile_url: [{ type: \\"URL\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { username, caption, Customtags, Autocomplete, tags, profile_url, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { username: modelFields.username, caption: modelFields.caption, tags: modelFields.tags, profile_url: modelFields.profile_url, }; await DataStore.save( Flex0.copyOf(flexRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyFlexUpdateForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || flexModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || flexModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <Grid columnGap=\\"inherit\\" rowGap=\\"inherit\\" templateColumns=\\"repeat(2, auto)\\" {...getOverrideProps(overrides, \\"RowGrid0\\")} > <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} placeholder=\\"john\\" value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username: value, caption, Customtags, Autocomplete, tags, profile_url, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} placeholder=\\"i love code\\" value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption: value, Customtags, Autocomplete, tags, profile_url, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> </Grid> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags: values, Autocomplete, tags, profile_url, }; const result = onChange(modelFields); values = result?.Customtags ?? values; } setCustomtags(values); setCurrentCustomtagsValue(\\"\\"); }} currentFieldValue={currentCustomtagsValue} label={\\"Tags\\"} items={Customtags} hasError={errors?.Customtags?.hasError} errorMessage={errors?.Customtags?.errorMessage} setFieldValue={setCurrentCustomtagsValue} inputFieldRef={CustomtagsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Tags\\" placeholder=\\"goals\\" value={currentCustomtagsValue} onChange={(e) => { let { value } = e.target; if (errors.Customtags?.hasError) { runValidationTasks(\\"Customtags\\", value); } setCurrentCustomtagsValue(value); }} onBlur={() => runValidationTasks(\\"Customtags\\", currentCustomtagsValue) } errorMessage={errors.Customtags?.errorMessage} hasError={errors.Customtags?.hasError} ref={CustomtagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"Customtags\\")} ></TextField> </ArrayField> <Autocomplete label=\\"Label\\" isRequired={false} options={[ { id: \\"option1\\", label: \\"option1\\", }, { id: \\"option2\\", label: \\"option2\\", }, ]} onSelect={({ id, label }) => { setAutocomplete(id); runValidationTasks(\\"Autocomplete\\", id); }} onClear={() => { setAutocomplete(\\"\\"); }} defaultValue={Autocomplete} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, Autocomplete: value, tags, profile_url, }; const result = onChange(modelFields); value = result?.Autocomplete ?? value; } if (errors.Autocomplete?.hasError) { runValidationTasks(\\"Autocomplete\\", value); } setAutocomplete(value); }} onBlur={() => runValidationTasks(\\"Autocomplete\\", Autocomplete)} errorMessage={errors.Autocomplete?.errorMessage} hasError={errors.Autocomplete?.hasError} labelHidden={false} {...getOverrideProps(overrides, \\"Autocomplete\\")} ></Autocomplete> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, Customtags, Autocomplete, tags: values, profile_url, }; const result = onChange(modelFields); values = result?.tags ?? values; } setTags(values); setCurrentTagsValue(\\"\\"); }} currentFieldValue={currentTagsValue} label={\\"Tags\\"} items={tags} hasError={errors?.tags?.hasError} errorMessage={errors?.tags?.errorMessage} setFieldValue={setCurrentTagsValue} inputFieldRef={tagsRef} defaultFieldValue={\\"\\"} > <TextField label=\\"Tags\\" isRequired={false} isReadOnly={false} value={currentTagsValue} onChange={(e) => { let { value } = e.target; if (errors.tags?.hasError) { runValidationTasks(\\"tags\\", value); } setCurrentTagsValue(value); }} onBlur={() => runValidationTasks(\\"tags\\", currentTagsValue)} errorMessage={errors.tags?.errorMessage} hasError={errors.tags?.hasError} ref={tagsRef} labelHidden={true} {...getOverrideProps(overrides, \\"tags\\")} ></TextField> </ArrayField> <TextField label=\\"Profile url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, Customtags, Autocomplete, tags, profile_url: value, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render a update form with colliding model name 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Flex as Flex0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyFlexUpdateFormInputValues = { username?: string; caption?: string; Customtags?: string[]; Autocomplete?: string; tags?: string[]; profile_url?: string; }; export declare type MyFlexUpdateFormValidationValues = { username?: ValidationFunction<string>; caption?: ValidationFunction<string>; Customtags?: ValidationFunction<string>; Autocomplete?: ValidationFunction<string>; tags?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyFlexUpdateFormOverridesProps = { MyFlexUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; RowGrid0?: PrimitiveOverrideProps<GridProps>; username?: PrimitiveOverrideProps<TextFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; Customtags?: PrimitiveOverrideProps<TextFieldProps>; Autocomplete?: PrimitiveOverrideProps<AutocompleteProps>; tags?: PrimitiveOverrideProps<TextFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; export declare type MyFlexUpdateFormProps = React.PropsWithChildren<{ overrides?: MyFlexUpdateFormOverridesProps | undefined | null; } & { id?: string; flex?: Flex0; onSubmit?: (fields: MyFlexUpdateFormInputValues) => MyFlexUpdateFormInputValues; onSuccess?: (fields: MyFlexUpdateFormInputValues) => void; onError?: (fields: MyFlexUpdateFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyFlexUpdateFormInputValues) => MyFlexUpdateFormInputValues; onValidate?: MyFlexUpdateFormValidationValues; } & React.CSSProperties>; export default function MyFlexUpdateForm(props: MyFlexUpdateFormProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Badge, Button, Divider, Flex, Grid, Icon, ScrollView, SelectField, Text, TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Post } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function PostCreateFormRow(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { username: \\"\\", caption: \\"\\", post_url: \\"\\", profile_url: \\"\\", status: \\"\\", metadata: \\"\\", nonModelField: \\"\\", nonModelFieldArray: [], }; const [username, setUsername] = React.useState(initialValues.username); const [caption, setCaption] = React.useState(initialValues.caption); const [post_url, setPost_url] = React.useState(initialValues.post_url); const [profile_url, setProfile_url] = React.useState( initialValues.profile_url ); const [status, setStatus] = React.useState(initialValues.status); const [metadata, setMetadata] = React.useState(initialValues.metadata); const [nonModelField, setNonModelField] = React.useState( initialValues.nonModelField ); const [nonModelFieldArray, setNonModelFieldArray] = React.useState( initialValues.nonModelFieldArray ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setUsername(initialValues.username); setCaption(initialValues.caption); setPost_url(initialValues.post_url); setProfile_url(initialValues.profile_url); setStatus(initialValues.status); setMetadata(initialValues.metadata); setNonModelField(initialValues.nonModelField); setNonModelFieldArray(initialValues.nonModelFieldArray); setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); const nonModelFieldArrayRef = React.createRef(); const validations = { username: [ { type: \\"GreaterThanChar\\", numValues: [2], validationMessage: \\"needs to be of length 2\\", }, ], caption: [], post_url: [{ type: \\"URL\\" }], profile_url: [{ type: \\"URL\\" }], status: [], metadata: [{ type: \\"JSON\\" }], nonModelField: [{ type: \\"JSON\\" }], nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { username, caption, post_url, profile_url, status, metadata, nonModelField, nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const modelFieldsToSave = { username: modelFields.username, caption: modelFields.caption, post_url: modelFields.post_url, profile_url: modelFields.profile_url, metadata: modelFields.metadata, nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => JSON.parse(s) ), nonModelField: modelFields.nonModelField ? JSON.parse(modelFields.nonModelField) : modelFields.nonModelField, }; await DataStore.save(new Post(modelFieldsToSave)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"PostCreateFormRow\\")} {...rest} > <Grid columnGap=\\"inherit\\" rowGap=\\"inherit\\" templateColumns=\\"repeat(2, auto)\\" {...getOverrideProps(overrides, \\"RowGrid0\\")} > <TextField label=\\"Username\\" isRequired={false} isReadOnly={false} placeholder=\\"john\\" value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username: value, caption, post_url, profile_url, status, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.username ?? value; } if (errors.username?.hasError) { runValidationTasks(\\"username\\", value); } setUsername(value); }} onBlur={() => runValidationTasks(\\"username\\", username)} errorMessage={errors.username?.errorMessage} hasError={errors.username?.hasError} {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField label=\\"Caption\\" isRequired={false} isReadOnly={false} placeholder=\\"i love code\\" value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption: value, post_url, profile_url, status, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.caption ?? value; } if (errors.caption?.hasError) { runValidationTasks(\\"caption\\", value); } setCaption(value); }} onBlur={() => runValidationTasks(\\"caption\\", caption)} errorMessage={errors.caption?.errorMessage} hasError={errors.caption?.hasError} {...getOverrideProps(overrides, \\"caption\\")} ></TextField> </Grid> <TextField label=\\"Post url\\" descriptiveText=\\"post url to use for the component\\" isRequired={false} isReadOnly={false} value={post_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, post_url: value, profile_url, status, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.post_url ?? value; } if (errors.post_url?.hasError) { runValidationTasks(\\"post_url\\", value); } setPost_url(value); }} onBlur={() => runValidationTasks(\\"post_url\\", post_url)} errorMessage={errors.post_url?.errorMessage} hasError={errors.post_url?.hasError} {...getOverrideProps(overrides, \\"post_url\\")} ></TextField> <TextField label=\\"Profile url\\" descriptiveText=\\"profile image url\\" isRequired={false} isReadOnly={false} value={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, post_url, profile_url: value, status, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.profile_url ?? value; } if (errors.profile_url?.hasError) { runValidationTasks(\\"profile_url\\", value); } setProfile_url(value); }} onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} errorMessage={errors.profile_url?.errorMessage} hasError={errors.profile_url?.hasError} {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> <SelectField label=\\"Label\\" placeholder=\\"Please select an option\\" value={status} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, post_url, profile_url, status: value, metadata, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.status ?? value; } if (errors.status?.hasError) { runValidationTasks(\\"status\\", value); } setStatus(value); }} onBlur={() => runValidationTasks(\\"status\\", status)} errorMessage={errors.status?.errorMessage} hasError={errors.status?.hasError} {...getOverrideProps(overrides, \\"status\\")} ></SelectField> <TextAreaField label=\\"Metadata\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, post_url, profile_url, status, metadata: value, nonModelField, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.metadata ?? value; } if (errors.metadata?.hasError) { runValidationTasks(\\"metadata\\", value); } setMetadata(value); }} onBlur={() => runValidationTasks(\\"metadata\\", metadata)} errorMessage={errors.metadata?.errorMessage} hasError={errors.metadata?.hasError} {...getOverrideProps(overrides, \\"metadata\\")} ></TextAreaField> <TextAreaField label=\\"Non model field\\" isRequired={false} isReadOnly={false} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { username, caption, post_url, profile_url, status, metadata, nonModelField: value, nonModelFieldArray, }; const result = onChange(modelFields); value = result?.nonModelField ?? value; } if (errors.nonModelField?.hasError) { runValidationTasks(\\"nonModelField\\", value); } setNonModelField(value); }} onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} errorMessage={errors.nonModelField?.errorMessage} hasError={errors.nonModelField?.hasError} {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { username, caption, post_url, profile_url, status, metadata, nonModelField, nonModelFieldArray: values, }; const result = onChange(modelFields); values = result?.nonModelFieldArray ?? values; } setNonModelFieldArray(values); setCurrentNonModelFieldArrayValue(\\"\\"); }} currentFieldValue={currentNonModelFieldArrayValue} label={\\"Non model field array\\"} items={nonModelFieldArray} hasError={errors?.nonModelFieldArray?.hasError} errorMessage={errors?.nonModelFieldArray?.errorMessage} setFieldValue={setCurrentNonModelFieldArrayValue} inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > <TextAreaField label=\\"Non model field array\\" isRequired={false} isReadOnly={false} value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; if (errors.nonModelFieldArray?.hasError) { runValidationTasks(\\"nonModelFieldArray\\", value); } setCurrentNonModelFieldArrayValue(value); }} onBlur={() => runValidationTasks( \\"nonModelFieldArray\\", currentNonModelFieldArrayValue ) } errorMessage={errors.nonModelFieldArray?.errorMessage} hasError={errors.nonModelFieldArray?.hasError} ref={nonModelFieldArrayRef} labelHidden={true} {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 2`] = ` "import * as React from \\"react\\"; import { GridProps, SelectFieldProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type PostCreateFormRowInputValues = { username?: string; caption?: string; post_url?: string; profile_url?: string; status?: string; metadata?: string; nonModelField?: string; nonModelFieldArray?: string[]; }; export declare type PostCreateFormRowValidationValues = { username?: ValidationFunction<string>; caption?: ValidationFunction<string>; post_url?: ValidationFunction<string>; profile_url?: ValidationFunction<string>; status?: ValidationFunction<string>; metadata?: ValidationFunction<string>; nonModelField?: ValidationFunction<string>; nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type PostCreateFormRowOverridesProps = { PostCreateFormRowGrid?: PrimitiveOverrideProps<GridProps>; RowGrid0?: PrimitiveOverrideProps<GridProps>; username?: PrimitiveOverrideProps<TextFieldProps>; caption?: PrimitiveOverrideProps<TextFieldProps>; post_url?: PrimitiveOverrideProps<TextFieldProps>; profile_url?: PrimitiveOverrideProps<TextFieldProps>; status?: PrimitiveOverrideProps<SelectFieldProps>; metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; export declare type PostCreateFormRowProps = React.PropsWithChildren<{ overrides?: PostCreateFormRowOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; onSuccess?: (fields: PostCreateFormRowInputValues) => void; onError?: (fields: PostCreateFormRowInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; onValidate?: PostCreateFormRowValidationValues; } & React.CSSProperties>; export default function PostCreateFormRow(props: PostCreateFormRowProps): React.ReactElement; " `; exports[`amplify form renderer tests datastore form tests should use proper field overrides for belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Member, Team as Team0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", teamID: undefined, Team: undefined, }; const [name, setName] = React.useState(initialValues.name); const [teamID, setTeamID] = React.useState(initialValues.teamID); const [Team, setTeam] = React.useState(initialValues.Team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setTeamID(initialValues.teamID); setCurrentTeamIDValue(undefined); setCurrentTeamIDDisplayValue(\\"\\"); setTeam(initialValues.Team); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = React.useState(\\"\\"); const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); const teamIDRef = React.createRef(); const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(\\"\\"); const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); const TeamRef = React.createRef(); const getIDValue = { Team: (r) => JSON.stringify({ id: r?.id }), }; const TeamIdSet = new Set( Array.isArray(Team) ? Team.map((r) => getIDValue.Team?.(r)) : getIDValue.Team?.(Team) ); const teamRecords = useDataStoreBinding({ type: \\"collection\\", model: Team0, }).items; const getDisplayValue = { teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, Team: (r) => r?.name, }; const validations = { name: [], teamID: [{ type: \\"Required\\" }], Team: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, teamID, Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Member(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"MyMemberForm\\")} {...rest} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Cancel\\" type=\\"button\\" onClick={() => { onCancel && onCancel(); }} {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, teamID, Team, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID: value, Team, }; const result = onChange(modelFields); value = result?.teamID ?? value; } setTeamID(value); setCurrentTeamIDValue(undefined); }} currentFieldValue={currentTeamIDValue} label={\\"Team id\\"} items={teamID ? [teamID] : []} hasError={errors?.teamID?.hasError} errorMessage={errors?.teamID?.errorMessage} getBadgeText={(value) => value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" } setFieldValue={(value) => { setCurrentTeamIDDisplayValue( value ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) : \\"\\" ); setCurrentTeamIDValue(value); }} inputFieldRef={teamIDRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team id\\" isRequired={true} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamIDDisplayValue} options={teamRecords .filter( (r, i, arr) => arr.findIndex((member) => member?.id === r?.id) === i ) .map((r) => ({ id: r?.id, label: getDisplayValue.teamID?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamIDValue(id); setCurrentTeamIDDisplayValue(label); runValidationTasks(\\"teamID\\", label); }} onClear={() => { setCurrentTeamIDDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.teamID?.hasError) { runValidationTasks(\\"teamID\\", value); } setCurrentTeamIDDisplayValue(value); setCurrentTeamIDValue(undefined); }} onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} errorMessage={errors.teamID?.errorMessage} hasError={errors.teamID?.hasError} ref={teamIDRef} labelHidden={true} {...getOverrideProps(overrides, \\"teamID\\")} ></Autocomplete> </ArrayField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, teamID, Team: value, }; const result = onChange(modelFields); value = result?.Team ?? value; } setTeam(value); setCurrentTeamValue(undefined); setCurrentTeamDisplayValue(\\"\\"); }} currentFieldValue={currentTeamValue} label={\\"Team Label\\"} items={Team ? [Team] : []} hasError={errors?.Team?.hasError} errorMessage={errors?.Team?.errorMessage} getBadgeText={getDisplayValue.Team} setFieldValue={(model) => { setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); setCurrentTeamValue(model); }} inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Team Label\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Team\\" value={currentTeamDisplayValue} options={teamRecords .filter((r) => !TeamIdSet.has(getIDValue.Team?.(r))) .map((r) => ({ id: getIDValue.Team?.(r), label: getDisplayValue.Team?.(r), }))} onSelect={({ id, label }) => { setCurrentTeamValue( teamRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentTeamDisplayValue(label); runValidationTasks(\\"Team\\", label); }} onClear={() => { setCurrentTeamDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.Team?.hasError) { runValidationTasks(\\"Team\\", value); } setCurrentTeamDisplayValue(value); setCurrentTeamValue(undefined); }} onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} errorMessage={errors.Team?.errorMessage} hasError={errors.Team?.hasError} ref={TeamRef} labelHidden={true} {...getOverrideProps(overrides, \\"Team\\")} ></Autocomplete> </ArrayField> </Grid> ); } " `; exports[`amplify form renderer tests datastore form tests should use proper field overrides for belongsTo relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Team as Team0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type MyMemberFormInputValues = { name?: string; teamID?: string; Team?: Team0; }; export declare type MyMemberFormValidationValues = { name?: ValidationFunction<string>; teamID?: ValidationFunction<string>; Team?: ValidationFunction<Team0>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type MyMemberFormOverridesProps = { MyMemberFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; teamID?: PrimitiveOverrideProps<AutocompleteProps>; Team?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type MyMemberFormProps = React.PropsWithChildren<{ overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onSuccess?: (fields: MyMemberFormInputValues) => void; onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; exports[`amplify form renderer tests forms with StorageField tests should render a create form with StorageField 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { Field, getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; import { fetchByPath, processFile, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; export default function CreateProductForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", imgKeys: [], }; const [name, setName] = React.useState(initialValues.name); const [imgKeys, setImgKeys] = React.useState(initialValues.imgKeys); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setImgKeys(initialValues.imgKeys); setErrors({}); }; const validations = { name: [], imgKeys: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, imgKeys, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save(new Product(modelFields)); if (onSuccess) { onSuccess(modelFields); } if (clearOnSuccess) { resetStateValues(); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"CreateProductForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, imgKeys, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <Field errorMessage={errors.imgKeys?.errorMessage} hasError={errors.imgKeys?.hasError} label={\\"Images\\"} isRequired={false} isReadOnly={false} > <StorageManager onUploadSuccess={({ key }) => { setImgKeys((prev) => { let value = [...prev, key]; if (onChange) { const modelFields = { name, imgKeys: value, }; const result = onChange(modelFields); value = result?.imgKeys ?? value; } return value; }); }} onFileRemove={({ key }) => { setImgKeys((prev) => { let value = prev.filter((f) => f !== key); if (onChange) { const modelFields = { name, imgKeys: value, }; const result = onChange(modelFields); value = result?.imgKeys ?? value; } return value; }); }} processFile={processFile} accessLevel={\\"protected\\"} acceptedFileTypes={[\\".txt\\", \\".pdf\\"]} isResumable={true} showThumbnails={false} maxFileCount={5} maxSize={1024} {...getOverrideProps(overrides, \\"imgKeys\\")} ></StorageManager> </Field> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Clear\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests forms with StorageField tests should render a update form with StorageField 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { Field, getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; import { fetchByPath, processFile, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; export default function UpdateProductForm(props) { const { id: idProp, product: productModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", imgKeys: [], }; const [name, setName] = React.useState(initialValues.name); const [imgKeys, setImgKeys] = React.useState(initialValues.imgKeys); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = productRecord ? { ...initialValues, ...productRecord } : initialValues; setName(cleanValues.name); setImgKeys(cleanValues.imgKeys ?? []); setErrors({}); }; const [productRecord, setProductRecord] = React.useState(productModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Product, idProp) : productModelProp; setProductRecord(record); }; queryData(); }, [idProp, productModelProp]); React.useEffect(resetStateValues, [productRecord]); const validations = { name: [], imgKeys: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, imgKeys, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save( Product.copyOf(productRecord, (updated) => { Object.assign(updated, modelFields); }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateProductForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, imgKeys, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <Field errorMessage={errors.imgKeys?.errorMessage} hasError={errors.imgKeys?.hasError} label={\\"Images\\"} isRequired={false} isReadOnly={false} > {productRecord && ( <StorageManager defaultFiles={productRecord.imgKeys.map((key) => ({ key }))} onUploadSuccess={({ key }) => { setImgKeys((prev) => { let value = [...prev, key]; if (onChange) { const modelFields = { name, imgKeys: value, }; const result = onChange(modelFields); value = result?.imgKeys ?? value; } return value; }); }} onFileRemove={({ key }) => { setImgKeys((prev) => { let value = prev.filter((f) => f !== key); if (onChange) { const modelFields = { name, imgKeys: value, }; const result = onChange(modelFields); value = result?.imgKeys ?? value; } return value; }); }} processFile={processFile} accessLevel={\\"private\\"} acceptedFileTypes={[\\".doc\\", \\".pdf\\"]} isResumable={false} showThumbnails={true} {...getOverrideProps(overrides, \\"imgKeys\\")} ></StorageManager> )} </Field> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || productModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || productModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests forms with StorageField tests should render a update form with StorageField 2`] = ` "import * as React from \\"react\\"; import { GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { StorageManagerProps } from \\"@aws-amplify/ui-react-storage\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateProductFormInputValues = { name?: string; imgKeys?: string[]; }; export declare type UpdateProductFormValidationValues = { name?: ValidationFunction<string>; imgKeys?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateProductFormOverridesProps = { UpdateProductFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; imgKeys?: PrimitiveOverrideProps<StorageManagerProps>; } & EscapeHatchProps; export declare type UpdateProductFormProps = React.PropsWithChildren<{ overrides?: UpdateProductFormOverridesProps | undefined | null; } & { id?: string; product?: Product; onSubmit?: (fields: UpdateProductFormInputValues) => UpdateProductFormInputValues; onSuccess?: (fields: UpdateProductFormInputValues) => void; onError?: (fields: UpdateProductFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateProductFormInputValues) => UpdateProductFormInputValues; onValidate?: UpdateProductFormValidationValues; } & React.CSSProperties>; export default function UpdateProductForm(props: UpdateProductFormProps): React.ReactElement; " `; exports[`amplify form renderer tests forms with StorageField tests should render a update form with StorageField on non-array field 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { Field, getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; import { fetchByPath, processFile, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; export default function UpdateProductForm(props) { const { id: idProp, product: productModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", singleImgKey: undefined, }; const [name, setName] = React.useState(initialValues.name); const [singleImgKey, setSingleImgKey] = React.useState( initialValues.singleImgKey ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = productRecord ? { ...initialValues, ...productRecord } : initialValues; setName(cleanValues.name); setSingleImgKey(cleanValues.singleImgKey); setErrors({}); }; const [productRecord, setProductRecord] = React.useState(productModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Product, idProp) : productModelProp; setProductRecord(record); }; queryData(); }, [idProp, productModelProp]); React.useEffect(resetStateValues, [productRecord]); const validations = { name: [], singleImgKey: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, singleImgKey, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks(fieldName, item) ) ); return promises; } promises.push( runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save( Product.copyOf(productRecord, (updated) => { Object.assign(updated, modelFields); }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateProductForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={false} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, singleImgKey, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <Field errorMessage={errors.singleImgKey?.errorMessage} hasError={errors.singleImgKey?.hasError} label={\\"Single Image\\"} descriptiveText={\\"Limited to One Image\\"} isRequired={false} isReadOnly={false} > {productRecord && ( <StorageManager defaultFiles={[{ key: productRecord.singleImgKey }]} onUploadSuccess={({ key }) => { setSingleImgKey((prev) => { let value = key; if (onChange) { const modelFields = { name, singleImgKey: value, }; const result = onChange(modelFields); value = result?.singleImgKey ?? value; } return value; }); }} onFileRemove={({ key }) => { setSingleImgKey((prev) => { let value = initialValues?.singleImgKey; if (onChange) { const modelFields = { name, singleImgKey: value, }; const result = onChange(modelFields); value = result?.singleImgKey ?? value; } return value; }); }} processFile={processFile} accessLevel={\\"protected\\"} acceptedFileTypes={[\\".txt\\", \\".pdf\\"]} isResumable={true} showThumbnails={false} maxFileCount={1} maxSize={1024} {...getOverrideProps(overrides, \\"singleImgKey\\")} ></StorageManager> )} </Field> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || productModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || productModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests should render form for child of bidirectional 1:m when field defined on parent 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Car, Dealership } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateCarForm(props) { const { id: idProp, car: carModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", dealership: undefined, }; const [name, setName] = React.useState(initialValues.name); const [dealership, setDealership] = React.useState(initialValues.dealership); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = carRecord ? { ...initialValues, ...carRecord, dealership } : initialValues; setName(cleanValues.name); setDealership(cleanValues.dealership); setCurrentDealershipValue(undefined); setCurrentDealershipDisplayValue(\\"\\"); setErrors({}); }; const [carRecord, setCarRecord] = React.useState(carModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Car, idProp) : carModelProp; setCarRecord(record); const dealershipRecord = record ? await record.dealership : undefined; setDealership(dealershipRecord); }; queryData(); }, [idProp, carModelProp]); React.useEffect(resetStateValues, [carRecord, dealership]); const [currentDealershipDisplayValue, setCurrentDealershipDisplayValue] = React.useState(\\"\\"); const [currentDealershipValue, setCurrentDealershipValue] = React.useState(undefined); const dealershipRef = React.createRef(); const getIDValue = { dealership: (r) => JSON.stringify({ id: r?.id }), }; const dealershipIdSet = new Set( Array.isArray(dealership) ? dealership.map((r) => getIDValue.dealership?.(r)) : getIDValue.dealership?.(dealership) ); const dealershipRecords = useDataStoreBinding({ type: \\"collection\\", model: Dealership, }).items; const getDisplayValue = { dealership: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], dealership: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, dealership, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); await DataStore.save( Car.copyOf(carRecord, (updated) => { Object.assign(updated, modelFields); if (!modelFields.dealership) { updated.dealershipId = undefined; } }) ); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateCarForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, dealership, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField lengthLimit={1} onChange={async (items) => { let value = items[0]; if (onChange) { const modelFields = { name, dealership: value, }; const result = onChange(modelFields); value = result?.dealership ?? value; } setDealership(value); setCurrentDealershipValue(undefined); setCurrentDealershipDisplayValue(\\"\\"); }} currentFieldValue={currentDealershipValue} label={\\"Dealership\\"} items={dealership ? [dealership] : []} hasError={errors?.dealership?.hasError} errorMessage={errors?.dealership?.errorMessage} getBadgeText={getDisplayValue.dealership} setFieldValue={(model) => { setCurrentDealershipDisplayValue( model ? getDisplayValue.dealership(model) : \\"\\" ); setCurrentDealershipValue(model); }} inputFieldRef={dealershipRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Dealership\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Dealership\\" value={currentDealershipDisplayValue} options={dealershipRecords .filter((r) => !dealershipIdSet.has(getIDValue.dealership?.(r))) .map((r) => ({ id: getIDValue.dealership?.(r), label: getDisplayValue.dealership?.(r), }))} onSelect={({ id, label }) => { setCurrentDealershipValue( dealershipRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentDealershipDisplayValue(label); runValidationTasks(\\"dealership\\", label); }} onClear={() => { setCurrentDealershipDisplayValue(\\"\\"); }} defaultValue={dealership} onChange={(e) => { let { value } = e.target; if (errors.dealership?.hasError) { runValidationTasks(\\"dealership\\", value); } setCurrentDealershipDisplayValue(value); setCurrentDealershipValue(undefined); }} onBlur={() => runValidationTasks(\\"dealership\\", currentDealershipDisplayValue) } errorMessage={errors.dealership?.errorMessage} hasError={errors.dealership?.hasError} ref={dealershipRef} labelHidden={true} {...getOverrideProps(overrides, \\"dealership\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || carModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || carModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests should render form for child of bidirectional 1:m when field defined on parent 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Car, Dealership } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateCarFormInputValues = { name?: string; dealership?: Dealership; }; export declare type UpdateCarFormValidationValues = { name?: ValidationFunction<string>; dealership?: ValidationFunction<Dealership>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateCarFormOverridesProps = { UpdateCarFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; dealership?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateCarFormProps = React.PropsWithChildren<{ overrides?: UpdateCarFormOverridesProps | undefined | null; } & { id?: string; car?: Car; onSubmit?: (fields: UpdateCarFormInputValues) => UpdateCarFormInputValues; onSuccess?: (fields: UpdateCarFormInputValues) => void; onError?: (fields: UpdateCarFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateCarFormInputValues) => UpdateCarFormInputValues; onValidate?: UpdateCarFormValidationValues; } & React.CSSProperties>; export default function UpdateCarForm(props: UpdateCarFormProps): React.ReactElement; " `; exports[`amplify form renderer tests should render form for parent of bidirectional 1:m when field defined on parent 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Autocomplete, Badge, Button, Divider, Flex, Grid, Icon, ScrollView, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { Dealership, Car } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ items = [], onChange, label, inputFieldRef, children, hasError, setFieldValue, currentFieldValue, defaultFieldValue, lengthLimit, getBadgeText, errorMessage, }) { const labelElement = <Text>{label}</Text>; const { tokens: { components: { fieldmessages: { error: errorStyles }, }, }, } = useTheme(); const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); const [isEditing, setIsEditing] = React.useState(); React.useEffect(() => { if (isEditing) { inputFieldRef?.current?.focus(); } }, [isEditing]); const removeItem = async (removeIndex) => { const newItems = items.filter((value, index) => index !== removeIndex); await onChange(newItems); setSelectedBadgeIndex(undefined); }; const addItem = async () => { if ( currentFieldValue !== undefined && currentFieldValue !== null && currentFieldValue !== \\"\\" && !hasError ) { const newItems = [...items]; if (selectedBadgeIndex !== undefined) { newItems[selectedBadgeIndex] = currentFieldValue; setSelectedBadgeIndex(undefined); } else { newItems.push(currentFieldValue); } await onChange(newItems); setIsEditing(false); } }; const arraySection = ( <React.Fragment> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { return ( <Badge key={index} style={{ cursor: \\"pointer\\", alignItems: \\"center\\", marginRight: 3, marginTop: 3, backgroundColor: index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", }} onClick={() => { setSelectedBadgeIndex(index); setFieldValue(items[index]); setIsEditing(true); }} > {getBadgeText ? getBadgeText(value) : value.toString()} <Icon style={{ cursor: \\"pointer\\", paddingLeft: 3, width: 20, height: 20, }} viewBox={{ width: 20, height: 20 }} paths={[ { d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", stroke: \\"black\\", }, ]} ariaLabel=\\"button\\" onClick={(event) => { event.stopPropagation(); removeItem(index); }} /> </Badge> ); })} </ScrollView> )} <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return ( <React.Fragment> {labelElement} {arraySection} </React.Fragment> ); } return ( <React.Fragment> {labelElement} {isEditing && children} {!isEditing ? ( <> <Button onClick={() => { setIsEditing(true); }} > Add item </Button> {errorMessage && hasError && ( <Text color={errorStyles.color} fontSize={errorStyles.fontSize}> {errorMessage} </Text> )} </> ) : ( <Flex justifyContent=\\"flex-end\\"> {(currentFieldValue || isEditing) && ( <Button children=\\"Cancel\\" type=\\"button\\" size=\\"small\\" onClick={() => { setFieldValue(defaultFieldValue); setIsEditing(false); setSelectedBadgeIndex(undefined); }} ></Button> )} <Button size=\\"small\\" variation=\\"link\\" isDisabled={hasError} onClick={addItem} > {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} </Button> </Flex> )} {arraySection} </React.Fragment> ); } export default function UpdateDealershipForm(props) { const { id: idProp, dealership: dealershipModelProp, onSuccess, onError, onSubmit, onValidate, onChange, overrides, ...rest } = props; const initialValues = { name: \\"\\", cars: [], }; const [name, setName] = React.useState(initialValues.name); const [cars, setCars] = React.useState(initialValues.cars); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = dealershipRecord ? { ...initialValues, ...dealershipRecord, cars: linkedCars } : initialValues; setName(cleanValues.name); setCars(cleanValues.cars ?? []); setCurrentCarsValue(undefined); setCurrentCarsDisplayValue(\\"\\"); setErrors({}); }; const [dealershipRecord, setDealershipRecord] = React.useState(dealershipModelProp); const [linkedCars, setLinkedCars] = React.useState([]); const canUnlinkCars = true; React.useEffect(() => { const queryData = async () => { const record = idProp ? await DataStore.query(Dealership, idProp) : dealershipModelProp; setDealershipRecord(record); const linkedCars = record ? await record.cars.toArray() : []; setLinkedCars(linkedCars); }; queryData(); }, [idProp, dealershipModelProp]); React.useEffect(resetStateValues, [dealershipRecord, linkedCars]); const [currentCarsDisplayValue, setCurrentCarsDisplayValue] = React.useState(\\"\\"); const [currentCarsValue, setCurrentCarsValue] = React.useState(undefined); const carsRef = React.createRef(); const getIDValue = { cars: (r) => JSON.stringify({ id: r?.id }), }; const carsIdSet = new Set( Array.isArray(cars) ? cars.map((r) => getIDValue.cars?.(r)) : getIDValue.cars?.(cars) ); const carRecords = useDataStoreBinding({ type: \\"collection\\", model: Car, }).items; const getDisplayValue = { cars: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [{ type: \\"Required\\" }], cars: [], }; const runValidationTasks = async ( fieldName, currentValue, getDisplayValue ) => { const value = currentValue && getDisplayValue ? getDisplayValue(currentValue) : currentValue; let validationResponse = validateField(value, validations[fieldName]); const customValidator = fetchByPath(onValidate, fieldName); if (customValidator) { validationResponse = await customValidator(value, validationResponse); } setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; return ( <Grid as=\\"form\\" rowGap=\\"15px\\" columnGap=\\"15px\\" padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); let modelFields = { name, cars, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => runValidationTasks( fieldName, item, getDisplayValue[fieldName] ) ) ); return promises; } promises.push( runValidationTasks( fieldName, modelFields[fieldName], getDisplayValue[fieldName] ) ); return promises; }, []) ); if (validationResponses.some((r) => r.hasError)) { return; } if (onSubmit) { modelFields = onSubmit(modelFields); } try { Object.entries(modelFields).forEach(([key, value]) => { if (typeof value === \\"string\\" && value === \\"\\") { modelFields[key] = null; } }); const promises = []; const carsToLink = []; const carsToUnLink = []; const carsSet = new Set(); const linkedCarsSet = new Set(); cars.forEach((r) => carsSet.add(getIDValue.cars?.(r))); linkedCars.forEach((r) => linkedCarsSet.add(getIDValue.cars?.(r))); linkedCars.forEach((r) => { if (!carsSet.has(getIDValue.cars?.(r))) { carsToUnLink.push(r); } }); cars.forEach((r) => { if (!linkedCarsSet.has(getIDValue.cars?.(r))) { carsToLink.push(r); } }); carsToUnLink.forEach((original) => { if (!canUnlinkCars) { throw Error( \`Car \${original.id} cannot be unlinked from Dealership because dealershipId is a required field.\` ); } promises.push( DataStore.save( Car.copyOf(original, (updated) => { updated.dealershipId = null; updated.dealership = null; }) ) ); }); carsToLink.forEach((original) => { promises.push( DataStore.save( Car.copyOf(original, (updated) => { updated.dealershipId = dealershipRecord.id; updated.dealership = dealershipRecord; }) ) ); }); const modelFieldsToSave = { name: modelFields.name, }; promises.push( DataStore.save( Dealership.copyOf(dealershipRecord, (updated) => { Object.assign(updated, modelFieldsToSave); }) ) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} {...getOverrideProps(overrides, \\"UpdateDealershipForm\\")} {...rest} > <TextField label=\\"Name\\" isRequired={true} isReadOnly={false} value={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { name: value, cars, }; const result = onChange(modelFields); value = result?.name ?? value; } if (errors.name?.hasError) { runValidationTasks(\\"name\\", value); } setName(value); }} onBlur={() => runValidationTasks(\\"name\\", name)} errorMessage={errors.name?.errorMessage} hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { name, cars: values, }; const result = onChange(modelFields); values = result?.cars ?? values; } setCars(values); setCurrentCarsValue(undefined); setCurrentCarsDisplayValue(\\"\\"); }} currentFieldValue={currentCarsValue} label={\\"Cars\\"} items={cars} hasError={errors?.cars?.hasError} errorMessage={errors?.cars?.errorMessage} getBadgeText={getDisplayValue.cars} setFieldValue={(model) => { setCurrentCarsDisplayValue(model ? getDisplayValue.cars(model) : \\"\\"); setCurrentCarsValue(model); }} inputFieldRef={carsRef} defaultFieldValue={\\"\\"} > <Autocomplete label=\\"Cars\\" isRequired={false} isReadOnly={false} placeholder=\\"Search Car\\" value={currentCarsDisplayValue} options={carRecords .filter((r) => !carsIdSet.has(getIDValue.cars?.(r))) .map((r) => ({ id: getIDValue.cars?.(r), label: getDisplayValue.cars?.(r), }))} onSelect={({ id, label }) => { setCurrentCarsValue( carRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); setCurrentCarsDisplayValue(label); runValidationTasks(\\"cars\\", label); }} onClear={() => { setCurrentCarsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; if (errors.cars?.hasError) { runValidationTasks(\\"cars\\", value); } setCurrentCarsDisplayValue(value); setCurrentCarsValue(undefined); }} onBlur={() => runValidationTasks(\\"cars\\", currentCarsDisplayValue)} errorMessage={errors.cars?.errorMessage} hasError={errors.cars?.hasError} ref={carsRef} labelHidden={true} {...getOverrideProps(overrides, \\"cars\\")} ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} isDisabled={!(idProp || dealershipModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ !(idProp || dealershipModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> </Flex> </Grid> ); } " `; exports[`amplify form renderer tests should render form for parent of bidirectional 1:m when field defined on parent 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { Dealership, Car } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; export declare type UpdateDealershipFormInputValues = { name?: string; cars?: Car[]; }; export declare type UpdateDealershipFormValidationValues = { name?: ValidationFunction<string>; cars?: ValidationFunction<Car>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type UpdateDealershipFormOverridesProps = { UpdateDealershipFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; cars?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; export declare type UpdateDealershipFormProps = React.PropsWithChildren<{ overrides?: UpdateDealershipFormOverridesProps | undefined | null; } & { id?: string; dealership?: Dealership; onSubmit?: (fields: UpdateDealershipFormInputValues) => UpdateDealershipFormInputValues; onSuccess?: (fields: UpdateDealershipFormInputValues) => void; onError?: (fields: UpdateDealershipFormInputValues, errorMessage: string) => void; onChange?: (fields: UpdateDealershipFormInputValues) => UpdateDealershipFormInputValues; onValidate?: UpdateDealershipFormValidationValues; } & React.CSSProperties>; export default function UpdateDealershipForm(props: UpdateDealershipFormProps): React.ReactElement; " `;