diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
index 273be168..240a4d68 100644
--- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
+++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
@@ -40445,6 +40445,515 @@ export default function UpdateOwnerForm(props: UpdateOwnerFormProps): React.Reac
"
`;
+exports[`amplify form renderer tests datastore form tests should 1:1 relationships without types file path - Create amplify js v6 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 { Owner, Dog as Dog0 } from \\"../models\\";
+import {
+ fetchByPath,
+ getOverrideProps,
+ useDataStoreBinding,
+ validateField,
+} from \\"./utils\\";
+import { DataStore } from \\"aws-amplify/datastore\\";
+function ArrayField({
+ items = [],
+ onChange,
+ label,
+ inputFieldRef,
+ children,
+ hasError,
+ setFieldValue,
+ currentFieldValue,
+ defaultFieldValue,
+ lengthLimit,
+ getBadgeText,
+ runValidationTasks,
+ 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 () => {
+ const { hasError } = runValidationTasks();
+ 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 [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 (
+ {
+ 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}
+ >
+ {
+ 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}
+ runValidationTasks={async () =>
+ await runValidationTasks(\\"Dog\\", currentDogValue)
+ }
+ 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),
+ }))}
+ 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\\")}
+ >
+
+
+
+
+
+
+
+
+ );
+}
+"
+`;
+
+exports[`amplify form renderer tests datastore form tests should 1:1 relationships without types file path - Create amplify js v6 2`] = `
+"import * as React from \\"react\\";
+import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\";
+import { Dog as Dog0 } from \\"../models\\";
+export declare type EscapeHatchProps = {
+ [elementHierarchy: string]: Record;
+} | null;
+export declare type VariantValues = {
+ [key: string]: string;
+};
+export declare type Variant = {
+ variantValues: VariantValues;
+ overrides: EscapeHatchProps;
+};
+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?: Dog0;
+};
+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 datastore form tests should generate a create form 1`] = `
"/* eslint-disable */
import * as React from \\"react\\";
diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
index 03200eee..11401015 100644
--- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
+++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
@@ -383,6 +383,19 @@ describe('amplify form renderer tests', () => {
expect(importCollection).toBeDefined();
});
+ it('should 1:1 relationships without types file path - Create amplify js v6', () => {
+ const { componentText, declaration } = generateWithAmplifyFormRenderer(
+ 'forms/owner-dog-create',
+ 'datastore/dog-owner-required',
+ { ...defaultCLIRenderConfig, dependencies: { 'aws-amplify': '^6.0.0' } },
+ { isNonModelSupported: true, isRelationshipSupported: true },
+ );
+
+ expect(componentText).toContain('import { DataStore } from "aws-amplify/datastore";');
+ expect(componentText).toMatchSnapshot();
+ expect(declaration).toMatchSnapshot();
+ });
+
describe('custom form tests', () => {
it('should render a custom backed create form', () => {
const { componentText, declaration } = generateWithAmplifyFormRenderer('forms/post-custom-create', undefined);
diff --git a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts
index d45a4d60..7686f862 100644
--- a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts
+++ b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts
@@ -34,7 +34,7 @@ import {
} from 'typescript';
import { ReactComponentRenderer } from '../react-component-renderer';
import { buildFormLayoutProperties, buildOpeningElementProperties } from '../react-component-render-helper';
-import { ImportCollection, ImportSource } from '../imports';
+import { ImportCollection, ImportSource, ImportValue } from '../imports';
import { buildExpression } from '../forms';
import { onSubmitValidationRun, buildModelFieldObject } from '../forms/form-renderer-helper';
import { hasTokenReference } from '../utils/forms/layout-helpers';
@@ -43,7 +43,8 @@ import { isModelDataType } from '../forms/form-renderer-helper/render-checkers';
import { replaceEmptyStringStatement } from '../forms/form-renderer-helper/cta-props';
import { ReactRenderConfig } from '../react-render-config';
import { defaultRenderConfig } from '../react-studio-template-renderer-helper';
-import { getAmplifyJSAPIImport } from '../helpers/amplify-js-versioning';
+import { getAmplifyJSAPIImport, getAmplifyJSVersionToRender } from '../helpers/amplify-js-versioning';
+import { AMPLIFY_JS_V6 } from '../utils/constants';
export default class FormRenderer extends ReactComponentRenderer {
constructor(
@@ -75,7 +76,11 @@ export default class FormRenderer extends ReactComponentRenderer