diff --git a/packages/react/spec/auto/form/PolarisAutoRelatedForm.stories.jsx b/packages/react/spec/auto/form/PolarisAutoRelatedForm.stories.jsx new file mode 100644 index 000000000..07a9de9cf --- /dev/null +++ b/packages/react/spec/auto/form/PolarisAutoRelatedForm.stories.jsx @@ -0,0 +1,53 @@ +import { AppProvider, Card, Page } from "@shopify/polaris"; +import translations from "@shopify/polaris/locales/en.json"; +import React from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { Provider } from "../../../src/GadgetProvider.tsx"; +import { PolarisAutoForm } from "../../../src/auto/polaris/PolarisAutoForm.tsx"; +import { PolarisAutoInput } from "../../../src/auto/polaris/inputs/PolarisAutoInput.tsx"; +import { PolarisAutoHasManyInput } from "../../../src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx"; +import { PolarisAutoRelatedForm } from "../../../src/auto/polaris/inputs/relationships/PolarisAutoRelatedForm.tsx"; +import { PolarisAutoSubmit } from "../../../src/auto/polaris/submit/PolarisAutoSubmit.tsx"; +import { testApi as api } from "../../apis.ts"; + +const ExampleAutoRelatedForm = (props) => { + return ( + + + + + + + + + ); +}; + +export default { + title: "Polaris/AutoForm/AutoRelatedForm", + component: ExampleAutoRelatedForm, + decorators: [ + (Story) => { + return ( + + + + + + + + + + + + ); + }, + ], +}; + +export const Primary = { + args: { + action: api.section.update, + findBy: "1", + }, +}; diff --git a/packages/react/src/auto/hooks/useBelongsToInputController.tsx b/packages/react/src/auto/hooks/useBelongsToInputController.tsx index e60f7cd4f..4f38989ad 100644 --- a/packages/react/src/auto/hooks/useBelongsToInputController.tsx +++ b/packages/react/src/auto/hooks/useBelongsToInputController.tsx @@ -3,7 +3,7 @@ import { useController } from "react-hook-form"; import { useAutoFormMetadata } from "../AutoFormContext.js"; import type { AutoRelationshipInputProps } from "../interfaces/AutoRelationshipInputProps.js"; import { useFieldMetadata } from "./useFieldMetadata.js"; -import { useRelatedModelOptions } from "./useRelatedModelOptions.js"; +import { useRelatedModelOptions } from "./useRelatedModel.js"; export const useBelongsToInputController = (props: AutoRelationshipInputProps) => { const { field, control } = props; diff --git a/packages/react/src/auto/hooks/useHasManyInputController.tsx b/packages/react/src/auto/hooks/useHasManyInputController.tsx index 2155b5e7c..0e9fcadbb 100644 --- a/packages/react/src/auto/hooks/useHasManyInputController.tsx +++ b/packages/react/src/auto/hooks/useHasManyInputController.tsx @@ -4,7 +4,7 @@ import type { GadgetHasManyConfig } from "../../internal/gql/graphql.js"; import { uniq } from "../../utils.js"; import type { AutoRelationshipInputProps } from "../interfaces/AutoRelationshipInputProps.js"; import { useFieldMetadata } from "./useFieldMetadata.js"; -import { useRelatedModelOptions } from "./useRelatedModelOptions.js"; +import { useRelatedModelOptions } from "./useRelatedModel.js"; export const useHasManyInputController = (props: AutoRelationshipInputProps) => { const { field } = props; diff --git a/packages/react/src/auto/hooks/useHasOneInputController.tsx b/packages/react/src/auto/hooks/useHasOneInputController.tsx index 77a39a846..e24e82bef 100644 --- a/packages/react/src/auto/hooks/useHasOneInputController.tsx +++ b/packages/react/src/auto/hooks/useHasOneInputController.tsx @@ -4,7 +4,7 @@ import type { GadgetHasOneConfig } from "../../internal/gql/graphql.js"; import { uniq } from "../../utils.js"; import type { AutoRelationshipInputProps } from "../interfaces/AutoRelationshipInputProps.js"; import { useFieldMetadata } from "./useFieldMetadata.js"; -import { useRelatedModelOptions } from "./useRelatedModelOptions.js"; +import { useRelatedModelOptions } from "./useRelatedModel.js"; export const useHasOneInputController = (props: AutoRelationshipInputProps) => { const { field } = props; diff --git a/packages/react/src/auto/hooks/useRelatedModelOptions.tsx b/packages/react/src/auto/hooks/useRelatedModel.tsx similarity index 91% rename from packages/react/src/auto/hooks/useRelatedModelOptions.tsx rename to packages/react/src/auto/hooks/useRelatedModel.tsx index 9f47fe3ff..084de3756 100644 --- a/packages/react/src/auto/hooks/useRelatedModelOptions.tsx +++ b/packages/react/src/auto/hooks/useRelatedModel.tsx @@ -13,10 +13,7 @@ import { useModelManager } from "./useModelManager.js"; export const optionRecordsToLoadCount = 25; export const selectedRecordsToLoadCount = 25; -export const useRelatedModelOptions = (props: { - field: string; // Field API identifier - optionLabel?: OptionLabel; // The label to display for each related model record -}) => { +export const useRelatedModelRecords = (props: { field: string }) => { const { field } = props; const { metadata } = useFieldMetadata(field); const { findBy, model } = useAutoFormMetadata(); @@ -30,11 +27,6 @@ export const useRelatedModelOptions = (props: { const relatedModelInverseFieldApiId = "inverseField" in relationshipFieldConfig ? relationshipFieldConfig.inverseField?.apiIdentifier : undefined; - const optionLabel = assert( - props.optionLabel ?? relationshipFieldConfig.relatedModel?.defaultDisplayField.apiIdentifier, - "Option label is required for relationships" - ); - const { selected } = isBelongsToField ? // eslint-disable-next-line useLinkedChildModelRelatedModelRecords({ @@ -54,8 +46,30 @@ export const useRelatedModelOptions = (props: { const relatedModelRecords = useAllRelatedModelRecords({ relatedModel: { apiIdentifier: relatedModelApiIdentifier!, namespace: relatedModelNamespace }, - optionLabel, + filter: isBelongsToField ? undefined : { [relatedModelInverseFieldApiId + "Id"]: { isSet: false } }, }); + + return { + selected, + relatedModelRecords, + }; +}; + +export const useRelatedModelOptions = (props: { + field: string; // Field API identifier + optionLabel?: OptionLabel; // The label to display for each related model record +}) => { + const { field } = props; + const { metadata } = useFieldMetadata(field); + + const relationshipFieldConfig = metadata.configuration as RelationshipFieldConfig; + + const optionLabel = assert( + props.optionLabel ?? relationshipFieldConfig.relatedModel?.defaultDisplayField.apiIdentifier, + "Option label is required for relationships" + ); + const { selected, relatedModelRecords } = useRelatedModelRecords(props); + const { relatedModel, pagination, search } = relatedModelRecords; const getOptions = () => { @@ -120,7 +134,7 @@ export const getRecordsAsOptions = (records: Record[], optionLabel: * * The lookup is done using the `findBy` to lookup on the current model to retrieve the related model record data */ -export const useLinkedChildModelRelatedModelRecords = (props: { +const useLinkedChildModelRelatedModelRecords = (props: { belongsToFieldApiId: string; currentRecordId?: string; currentModel: { apiIdentifier: string; namespace?: string[] | string | null }; @@ -154,7 +168,7 @@ export const useLinkedChildModelRelatedModelRecords = (props: { /** * For getting the related child model records in a HasOne/HasMany relationship */ -export const useLinkedParentModelRelatedModelRecords = (props: { +const useLinkedParentModelRelatedModelRecords = (props: { relatedModel: { apiIdentifier: string; namespace?: string[] | string | null; @@ -177,7 +191,7 @@ export const useLinkedParentModelRelatedModelRecords = (props: { pause: !currentRecordId, // HasOne/HasMany need the current record to query the inverse field in the related model first: selectedRecordsToLoadCount, // Many records can point to the current record in hasOne/hasMany - filter: { [inverseFieldApiIdentifier]: { equals: currentRecordId } }, // Filter by the inverse field belongsTo field value + filter: { [inverseFieldApiIdentifier + "Id"]: { equals: currentRecordId } }, // Filter by the inverse field belongsTo field value }); return { @@ -189,8 +203,9 @@ export const useLinkedParentModelRelatedModelRecords = (props: { }; }; -export const useAllRelatedModelRecords = (props: { +const useAllRelatedModelRecords = (props: { optionLabel?: OptionLabel; + filter?: Record; relatedModel: { apiIdentifier: string; namespace?: string[] | string | null }; }) => { const { optionLabel, relatedModel } = props; @@ -204,6 +219,7 @@ export const useAllRelatedModelRecords = (props: { const [{ data: newlyFetchedRecords, fetching, error }, _refetch] = useFindMany(relatedModelManager as any, { first: optionRecordsToLoadCount, + ...(props.filter && { filter: props.filter }), ...(paginationPage && { after: paginationPage }), ...(searchValue && { search: searchValue }), ...(optionLabelIsFieldName && { select: { id: true, [optionLabel]: true } }), diff --git a/packages/react/src/auto/polaris/index.ts b/packages/react/src/auto/polaris/index.ts index 0a29fce3e..b80a703e9 100644 --- a/packages/react/src/auto/polaris/index.ts +++ b/packages/react/src/auto/polaris/index.ts @@ -20,5 +20,6 @@ export { } from "./inputs/PolarisAutoTextInput.js"; export { PolarisAutoBelongsToInput as AutoBelongsToInput } from "./inputs/relationships/PolarisAutoBelongsToInput.js"; export { PolarisAutoHasManyInput as AutoHasManyInput } from "./inputs/relationships/PolarisAutoHasManyInput.js"; +export { PolarisAutoRelatedForm as AutoRelatedForm } from "./inputs/relationships/PolarisAutoRelatedForm.js"; export { PolarisAutoSubmit as AutoSubmit } from "./submit/PolarisAutoSubmit.js"; export { PolarisSubmitResultBanner as SubmitResultBanner } from "./submit/PolarisSubmitResultBanner.js"; diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx index b3b872804..ce45857d1 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx @@ -1,7 +1,7 @@ import { Combobox, Tag } from "@shopify/polaris"; import React from "react"; import { useBelongsToInputController } from "../../../hooks/useBelongsToInputController.js"; -import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModelOptions.js"; +import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModel.js"; import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; import { RelatedModelOptions } from "./RelatedModelOptions.js"; diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx index 02cc3ec43..582dfca57 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx @@ -2,7 +2,7 @@ import type { GadgetRecordList } from "@gadgetinc/api-client-core"; import { Banner, Combobox } from "@shopify/polaris"; import React from "react"; import { useHasManyInputController } from "../../../hooks/useHasManyInputController.js"; -import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModelOptions.js"; +import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModel.js"; import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; import { RelatedModelOptions } from "./RelatedModelOptions.js"; import { getSelectedRelatedRecordTags } from "./SelectedRelatedRecordTags.js"; diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx index c08fed9b8..31b185be1 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx @@ -1,7 +1,7 @@ import { Banner, Combobox } from "@shopify/polaris"; import React from "react"; import { useHasOneInputController } from "../../../hooks/useHasOneInputController.js"; -import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModelOptions.js"; +import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModel.js"; import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; import { RelatedModelOptions } from "./RelatedModelOptions.js"; import { getSelectedRelatedRecordTags } from "./SelectedRelatedRecordTags.js"; diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoRelatedForm.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoRelatedForm.tsx new file mode 100644 index 000000000..b9bdb9bf3 --- /dev/null +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoRelatedForm.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { useFormContext } from "react-hook-form"; +import { useFieldMetadata } from "../../../hooks/useFieldMetadata.js"; +import { useRelatedModelRecords } from "../../../hooks/useRelatedModel.js"; + +export const PolarisAutoRelatedForm = (props: { field: string; children: React.ReactNode }) => { + const { field } = props; + const { getValues } = useFormContext(); + const { metadata, path } = useFieldMetadata(field); + const { selected, relatedModelRecords } = useRelatedModelRecords(props); + + const values = getValues(path); + + console.log({ values, path, field, selected, relatedModelRecords, metadata }, "foo foo fas"); + + return
hi
; +}; diff --git a/packages/react/src/use-action-form/utils.ts b/packages/react/src/use-action-form/utils.ts index ffe158ba7..c96c9d589 100644 --- a/packages/react/src/use-action-form/utils.ts +++ b/packages/react/src/use-action-form/utils.ts @@ -390,6 +390,8 @@ const getParentRelationshipFieldGraphqlApiInput = (props: { input: any; result: const { input, result } = props; const { __typename, ...rest } = result; + console.log({ input, result }); + if ("__id" in rest) { if ("__unlinkedInverseField" in rest && rest.__unlinkedInverseField) { const inverseFieldApiId = rest.__unlinkedInverseField;