From f85e96acacca4639c4cfce2aab0c96bb103a2e6b Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Thu, 14 Mar 2024 16:53:24 -0400 Subject: [PATCH] fix(ui): executes filterOptions on the server (#5335) --- packages/payload/src/admin/forms/Form.ts | 6 ++ packages/payload/src/admin/types.ts | 3 +- .../payload/src/config/createClientConfig.ts | 7 +- .../src/elements/GetFilterOptions/index.tsx | 64 ------------------- packages/ui/src/elements/ListDrawer/types.ts | 4 +- packages/ui/src/exports/forms.ts | 6 +- .../addFieldStatePromise.ts | 26 ++++++++ .../buildStateFromSchema.spec.js | 1 + .../getFilterOptionsQuery.ts | 6 ++ .../buildStateFromSchema/index.ts | 4 +- .../buildStateFromSchema/iterateFields.ts | 0 .../src/forms/fields/Relationship/index.tsx | 27 +++----- .../ui/src/forms/fields/Relationship/types.ts | 5 -- packages/ui/src/forms/fields/Upload/Input.tsx | 19 ++---- packages/ui/src/forms/fields/Upload/index.tsx | 3 +- packages/ui/src/forms/fields/shared.ts | 61 +++++++++--------- packages/ui/src/forms/useField/index.tsx | 3 + packages/ui/src/forms/useField/types.ts | 3 +- packages/ui/src/utilities/getFormState.ts | 2 +- test/_community/collections/Posts/index.ts | 11 ++++ tsconfig.json | 2 +- 21 files changed, 112 insertions(+), 151 deletions(-) delete mode 100644 packages/ui/src/elements/GetFilterOptions/index.tsx rename packages/ui/src/forms/{utilities => }/buildStateFromSchema/addFieldStatePromise.ts (95%) rename packages/ui/src/forms/{utilities => }/buildStateFromSchema/buildStateFromSchema.spec.js (97%) rename packages/ui/src/forms/{fields => buildStateFromSchema}/getFilterOptionsQuery.ts (99%) rename packages/ui/src/forms/{utilities => }/buildStateFromSchema/index.ts (90%) rename packages/ui/src/forms/{utilities => }/buildStateFromSchema/iterateFields.ts (100%) diff --git a/packages/payload/src/admin/forms/Form.ts b/packages/payload/src/admin/forms/Form.ts index 96d08d941..8b9f65770 100644 --- a/packages/payload/src/admin/forms/Form.ts +++ b/packages/payload/src/admin/forms/Form.ts @@ -1,4 +1,5 @@ import type { ClientValidate, Field } from '../../fields/config/types.js' +import type { Where } from '../../types/index.js' export type Data = { [key: string]: any @@ -11,11 +12,16 @@ export type Row = { id: string } +export type FilterOptionsResult = { + [relation: string]: Where | boolean +} + export type FormField = { disableFormData?: boolean errorMessage?: string errorPaths?: Set fieldSchema?: Field + filterOptions?: FilterOptionsResult initialValue: unknown passesCondition?: boolean rows?: Row[] diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index ac84542f9..e74c9c33d 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -23,9 +23,10 @@ export type { DescriptionComponent, DescriptionFunction, } from './forms/FieldDescription.js' -export type { Data, FormField, FormState, Row } from './forms/Form.js' +export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js' export type { LabelProps } from './forms/Label.js' export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js' + export type { AdminViewComponent, AdminViewProps, diff --git a/packages/payload/src/config/createClientConfig.ts b/packages/payload/src/config/createClientConfig.ts index 9779cd2cc..b3a1fcea4 100644 --- a/packages/payload/src/config/createClientConfig.ts +++ b/packages/payload/src/config/createClientConfig.ts @@ -32,7 +32,8 @@ export type ServerOnlyGlobalAdminProperties = keyof Pick< export type ServerOnlyLivePreviewProperties = keyof Pick export type ServerOnlyFieldProperties = - | 'editor' + | 'editor' // This is a `richText` only property + | 'filterOptions' // This is a `relationship` and `upload` only property | 'label' | keyof Pick @@ -79,8 +80,8 @@ export const sanitizeField = (f: Field) => { 'validate', 'defaultValue', 'label', - // This is a `richText` only property - 'editor', + 'filterOptions', // This is a `relationship` and `upload` only property + 'editor', // This is a `richText` only property // `fields` // `blocks` // `tabs` diff --git a/packages/ui/src/elements/GetFilterOptions/index.tsx b/packages/ui/src/elements/GetFilterOptions/index.tsx deleted file mode 100644 index f6b067153..000000000 --- a/packages/ui/src/elements/GetFilterOptions/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { FilterOptions } from 'payload/types' - -import equal from 'deep-equal' -import { useEffect } from 'react' - -import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js' - -import { useAllFormFields } from '../../forms/Form/context.js' -import getSiblingData from '../../forms/Form/getSiblingData.js' -import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues.js' -import { getFilterOptionsQuery } from '../../forms/fields/getFilterOptionsQuery.js' -import { useAuth } from '../../providers/Auth/index.js' -import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' - -type Args = { - filterOptions: FilterOptions - filterOptionsResult: FilterOptionsResult - path: string - relationTo: string | string[] - setFilterOptionsResult: (optionFilters: FilterOptionsResult) => void -} - -export const GetFilterOptions = ({ - filterOptions, - filterOptionsResult, - path, - relationTo, - setFilterOptionsResult, -}: Args): null => { - const [fields] = useAllFormFields() - const { id } = useDocumentInfo() - const { user } = useAuth() - - useEffect(() => { - const data = reduceFieldsToValues(fields, true) - const siblingData = getSiblingData(fields, path) - - const getFilterOptions = async () => { - const newFilterOptionsResult = await getFilterOptionsQuery(filterOptions, { - id, - data, - relationTo, - siblingData, - user, - }) - - if (!equal(newFilterOptionsResult, filterOptionsResult)) { - setFilterOptionsResult(newFilterOptionsResult) - } - } - getFilterOptions() - }, [ - fields, - filterOptions, - id, - relationTo, - user, - path, - filterOptionsResult, - setFilterOptionsResult, - ]) - - return null -} diff --git a/packages/ui/src/elements/ListDrawer/types.ts b/packages/ui/src/elements/ListDrawer/types.ts index 8696c2be9..f70604bd1 100644 --- a/packages/ui/src/elements/ListDrawer/types.ts +++ b/packages/ui/src/elements/ListDrawer/types.ts @@ -1,9 +1,7 @@ -import type { SanitizedCollectionConfig } from 'payload/types' +import type { FilterOptionsResult, SanitizedCollectionConfig } from 'payload/types' import type React from 'react' import type { HTMLAttributes } from 'react' -import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js' - export type ListDrawerProps = { collectionSlugs: string[] customHeader?: React.ReactNode diff --git a/packages/ui/src/exports/forms.ts b/packages/ui/src/exports/forms.ts index 21cca68a2..c0e38e5a0 100644 --- a/packages/ui/src/exports/forms.ts +++ b/packages/ui/src/exports/forms.ts @@ -21,6 +21,8 @@ export { RenderFields } from '../forms/RenderFields/index.js' export { useRowLabel } from '../forms/RowLabel/Context/index.js' export { default as FormSubmit } from '../forms/Submit/index.js' export { default as Submit } from '../forms/Submit/index.js' +export { buildStateFromSchema } from '../forms/buildStateFromSchema/index.js' +export type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js' export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js' export { CheckboxInput } from '../forms/fields/Checkbox/Input.js' export { default as Checkbox } from '../forms/fields/Checkbox/index.js' @@ -38,16 +40,14 @@ export { default as Text } from '../forms/fields/Text/index.js' export type { Props as TextFieldProps } from '../forms/fields/Text/types.js' export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js' export { default as Textarea } from '../forms/fields/Textarea/index.js' + export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js' export { default as UploadField } from '../forms/fields/Upload/index.js' - export { fieldTypes } from '../forms/fields/index.js' export { fieldBaseClass } from '../forms/fields/shared.js' export { useField } from '../forms/useField/index.js' export type { FieldType, Options } from '../forms/useField/types.js' -export { default as buildStateFromSchema } from '../forms/utilities/buildStateFromSchema/index.js' -export type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js' export { withCondition } from '../forms/withCondition/index.js' export { buildComponentMap } from '../utilities/buildComponentMap/index.js' diff --git a/packages/ui/src/forms/utilities/buildStateFromSchema/addFieldStatePromise.ts b/packages/ui/src/forms/buildStateFromSchema/addFieldStatePromise.ts similarity index 95% rename from packages/ui/src/forms/utilities/buildStateFromSchema/addFieldStatePromise.ts rename to packages/ui/src/forms/buildStateFromSchema/addFieldStatePromise.ts index 310d33e10..33ae11190 100644 --- a/packages/ui/src/forms/utilities/buildStateFromSchema/addFieldStatePromise.ts +++ b/packages/ui/src/forms/buildStateFromSchema/addFieldStatePromise.ts @@ -11,6 +11,7 @@ import ObjectIdImport from 'bson-objectid' import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types' import { getDefaultValue } from 'payload/utilities' +import { getFilterOptionsQuery } from './getFilterOptionsQuery.js' import { iterateFields } from './iterateFields.js' const ObjectId = (ObjectIdImport.default || @@ -92,6 +93,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom if (fieldAffectsData(field)) { const validate = operation === 'update' ? field.validate : undefined + const fieldState: FormField = { errorPaths: new Set(), fieldSchema: includeSchema ? field : undefined, @@ -375,6 +377,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom } case 'relationship': { + if (typeof field.filterOptions === 'function') { + const query = await getFilterOptionsQuery(field.filterOptions, { + id, + data: fullData, + relationTo: field.relationTo, + siblingData: data, + user: req.user, + }) + + fieldState.filterOptions = query + } + if (field.hasMany) { const relationshipValue = Array.isArray(valueWithDefault) ? valueWithDefault.map((relationship) => { @@ -433,6 +447,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom } case 'upload': { + if (typeof field.filterOptions === 'function') { + const query = await getFilterOptionsQuery(field.filterOptions, { + id, + data: fullData, + relationTo: field.relationTo, + siblingData: data, + user: req.user, + }) + + fieldState.filterOptions = query + } + const relationshipValue = valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault ? valueWithDefault.id diff --git a/packages/ui/src/forms/utilities/buildStateFromSchema/buildStateFromSchema.spec.js b/packages/ui/src/forms/buildStateFromSchema/buildStateFromSchema.spec.js similarity index 97% rename from packages/ui/src/forms/utilities/buildStateFromSchema/buildStateFromSchema.spec.js rename to packages/ui/src/forms/buildStateFromSchema/buildStateFromSchema.spec.js index 59b4137be..03fff3c70 100644 --- a/packages/ui/src/forms/utilities/buildStateFromSchema/buildStateFromSchema.spec.js +++ b/packages/ui/src/forms/buildStateFromSchema/buildStateFromSchema.spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ import buildStateFromSchema from './index.js' describe('Form - buildStateFromSchema', () => { diff --git a/packages/ui/src/forms/fields/getFilterOptionsQuery.ts b/packages/ui/src/forms/buildStateFromSchema/getFilterOptionsQuery.ts similarity index 99% rename from packages/ui/src/forms/fields/getFilterOptionsQuery.ts rename to packages/ui/src/forms/buildStateFromSchema/getFilterOptionsQuery.ts index d7dd8e764..0a0aec002 100644 --- a/packages/ui/src/forms/fields/getFilterOptionsQuery.ts +++ b/packages/ui/src/forms/buildStateFromSchema/getFilterOptionsQuery.ts @@ -5,8 +5,11 @@ export const getFilterOptionsQuery = async ( options: Omit & { relationTo: string | string[] }, ): Promise<{ [collection: string]: Where }> => { const { relationTo } = options + const relations = Array.isArray(relationTo) ? relationTo : [relationTo] + const query = {} + if (typeof filterOptions !== 'undefined') { await Promise.all( relations.map(async (relation) => { @@ -14,9 +17,11 @@ export const getFilterOptionsQuery = async ( typeof filterOptions === 'function' ? await filterOptions({ ...options, relationTo: relation }) : filterOptions + if (query[relation] === true) { query[relation] = {} } + // this is an ugly way to prevent results from being returned if (query[relation] === false) { query[relation] = { id: { exists: false } } @@ -24,5 +29,6 @@ export const getFilterOptionsQuery = async ( }), ) } + return query } diff --git a/packages/ui/src/forms/utilities/buildStateFromSchema/index.ts b/packages/ui/src/forms/buildStateFromSchema/index.ts similarity index 90% rename from packages/ui/src/forms/utilities/buildStateFromSchema/index.ts rename to packages/ui/src/forms/buildStateFromSchema/index.ts index c1c0dd85a..b48077171 100644 --- a/packages/ui/src/forms/utilities/buildStateFromSchema/index.ts +++ b/packages/ui/src/forms/buildStateFromSchema/index.ts @@ -24,7 +24,7 @@ export type BuildFormStateArgs = { schemaPath: string } -const buildStateFromSchema = async (args: Args): Promise => { +export const buildStateFromSchema = async (args: Args): Promise => { const { id, data: fullData = {}, fieldSchema, operation, preferences, req } = args if (fieldSchema) { @@ -49,5 +49,3 @@ const buildStateFromSchema = async (args: Args): Promise => { return {} } - -export default buildStateFromSchema diff --git a/packages/ui/src/forms/utilities/buildStateFromSchema/iterateFields.ts b/packages/ui/src/forms/buildStateFromSchema/iterateFields.ts similarity index 100% rename from packages/ui/src/forms/utilities/buildStateFromSchema/iterateFields.ts rename to packages/ui/src/forms/buildStateFromSchema/iterateFields.ts diff --git a/packages/ui/src/forms/fields/Relationship/index.tsx b/packages/ui/src/forms/fields/Relationship/index.tsx index a806caf34..b0a5edb0b 100644 --- a/packages/ui/src/forms/fields/Relationship/index.tsx +++ b/packages/ui/src/forms/fields/Relationship/index.tsx @@ -7,9 +7,8 @@ import qs from 'qs' import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react' import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js' -import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types.js' +import type { GetResults, Option, Props, Value } from './types.js' -import { GetFilterOptions } from '../../../elements/GetFilterOptions/index.js' import ReactSelect from '../../../elements/ReactSelect/index.js' import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js' import { useAuth } from '../../../providers/Auth/index.js' @@ -57,7 +56,6 @@ const Relationship: React.FC = (props) => { const relationTo = 'relationTo' in props ? props?.relationTo : undefined const hasMany = 'hasMany' in props ? props?.hasMany : undefined - const filterOptions = 'filterOptions' in props ? props?.filterOptions : undefined const sortOptions = 'sortOptions' in props ? props?.sortOptions : undefined const isSortable = 'isSortable' in props ? props?.isSortable : true const allowCreate = 'allowCreate' in props ? props?.allowCreate : true @@ -71,7 +69,6 @@ const Relationship: React.FC = (props) => { const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1) const [lastLoadedPage, setLastLoadedPage] = useState>({}) const [errorLoading, setErrorLoading] = useState('') - const [filterOptionsResult, setFilterOptionsResult] = useState() const [search, setSearch] = useState('') const [isLoading, setIsLoading] = useState(false) const [hasLoadedFirstPage, setHasLoadedFirstPage] = useState(false) @@ -87,7 +84,9 @@ const Relationship: React.FC = (props) => { [validate, required], ) - const { initialValue, path, setValue, showError, value } = useField({ + const { filterOptions, initialValue, path, setValue, showError, value } = useField< + Value | Value[] + >({ path: pathFromProps || name, validate: memoizedValidate, }) @@ -123,7 +122,8 @@ const Relationship: React.FC = (props) => { if (!errorLoading) { await relationsToFetch.reduce(async (priorRelation, relation) => { - const relationFilterOption = filterOptionsResult?.[relation] + const relationFilterOption = filterOptions?.[relation] + let lastLoadedPageToUse if (search !== searchArg) { lastLoadedPageToUse = 1 @@ -246,7 +246,7 @@ const Relationship: React.FC = (props) => { lastLoadedPage, collections, locale, - filterOptionsResult, + filterOptions, serverURL, sortOptions, api, @@ -362,7 +362,7 @@ const Relationship: React.FC = (props) => { setEnableWordBoundarySearch(!isIdOnly) }, [relationTo, collections]) - // When (`relationTo` || `filterOptionsResult` || `locale`) changes, reset component + // When (`relationTo` || `filterOptions` || `locale`) changes, reset component // Note - effect should not run on first run useEffect(() => { if (firstRun.current) { @@ -374,7 +374,7 @@ const Relationship: React.FC = (props) => { setLastFullyLoadedRelation(-1) setLastLoadedPage({}) setHasLoadedFirstPage(false) - }, [relationTo, filterOptionsResult, locale]) + }, [relationTo, filterOptions, locale]) const onSave = useCallback( (args) => { @@ -435,15 +435,6 @@ const Relationship: React.FC = (props) => { > {Error} {Label} - {!errorLoading && (
Promise - -export type FilterOptionsResult = { - [relation: string]: Where | boolean -} diff --git a/packages/ui/src/forms/fields/Upload/Input.tsx b/packages/ui/src/forms/fields/Upload/Input.tsx index b280a7e48..d1b2f955b 100644 --- a/packages/ui/src/forms/fields/Upload/Input.tsx +++ b/packages/ui/src/forms/fields/Upload/Input.tsx @@ -1,13 +1,12 @@ 'use client' -import type { SanitizedCollectionConfig, UploadField } from 'payload/types' +import type { FilterOptionsResult, SanitizedCollectionConfig, UploadField } from 'payload/types' import { getTranslation } from '@payloadcms/translations' import React, { useCallback, useEffect, useState } from 'react' import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js' import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js' -import type { FilterOptionsResult } from '../Relationship/types.js' import { Button } from '../../../elements/Button/index.js' import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js' @@ -23,7 +22,7 @@ const baseClass = 'upload' export type UploadInputProps = FormFieldBase & { api?: string collection?: SanitizedCollectionConfig - filterOptions?: UploadField['filterOptions'] + filterOptions?: FilterOptionsResult onChange?: (e) => void relationTo?: UploadField['relationTo'] serverURL?: string @@ -34,12 +33,12 @@ export type UploadInputProps = FormFieldBase & { export const UploadInput: React.FC = (props) => { const { Description, - Error, Label: LabelFromProps, api = '/api', className, collection, + filterOptions, label, onChange, readOnly, @@ -59,7 +58,6 @@ export const UploadInput: React.FC = (props) => { const [file, setFile] = useState(undefined) const [missingFile, setMissingFile] = useState(false) const [collectionSlugs] = useState([collection?.slug]) - const [filterOptionsResult, setFilterOptionsResult] = useState() const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({ collectionSlug: collectionSlugs[0], @@ -67,7 +65,7 @@ export const UploadInput: React.FC = (props) => { const [ListDrawer, ListDrawerToggler, { closeDrawer: closeListDrawer }] = useListDrawer({ collectionSlugs, - filterOptions: filterOptionsResult, + filterOptions, }) useEffect(() => { @@ -131,15 +129,6 @@ export const UploadInput: React.FC = (props) => { width, }} > - {/* */} {Error} {Label} {collection?.upload && ( diff --git a/packages/ui/src/forms/fields/Upload/index.tsx b/packages/ui/src/forms/fields/Upload/index.tsx index 3748c542f..199aa75c3 100644 --- a/packages/ui/src/forms/fields/Upload/index.tsx +++ b/packages/ui/src/forms/fields/Upload/index.tsx @@ -15,7 +15,6 @@ const Upload: React.FC = (props) => { Error, Label: LabelFromProps, className, - filterOptions, label, path: pathFromProps, readOnly, @@ -45,7 +44,7 @@ const Upload: React.FC = (props) => { [validate, required], ) - const { path, setValue, showError, value } = useField({ + const { filterOptions, path, setValue, showError, value } = useField({ path: pathFromProps, validate: memoizedValidate, }) diff --git a/packages/ui/src/forms/fields/shared.ts b/packages/ui/src/forms/fields/shared.ts index 637b64879..115f05bdb 100644 --- a/packages/ui/src/forms/fields/shared.ts +++ b/packages/ui/src/forms/fields/shared.ts @@ -1,4 +1,4 @@ -import type { FieldPermissions, User } from 'payload/auth' +import type { User } from 'payload/auth' import type { Locale, SanitizedLocalizationConfig } from 'payload/config' import type { ArrayField, @@ -51,6 +51,21 @@ export type FormFieldBase = { validate?: Validate width?: string } & ( + | { + // For `array` fields + label?: RowLabel + labels?: ArrayField['labels'] + maxRows?: ArrayField['maxRows'] + minRows?: ArrayField['minRows'] + } + | { + // For `blocks` fields + blocks?: ReducedBlock[] + labels?: BlockField['labels'] + maxRows?: BlockField['maxRows'] + minRows?: BlockField['minRows'] + slug?: string + } | { // For `code` fields editorOptions?: CodeField['admin']['editorOptions'] @@ -68,11 +83,25 @@ export type FormFieldBase = { // For `json` fields editorOptions?: JSONField['admin']['editorOptions'] } + | { + // For `number` fields + hasMany?: boolean + max?: number + maxRows?: number + min?: number + step?: number + } | { // For `radio` fields layout?: 'horizontal' | 'vertical' options?: Option[] } + | { + // For `relationship` fields + allowCreate?: RelationshipField['admin']['allowCreate'] + relationTo?: RelationshipField['relationTo'] + sortOptions?: RelationshipField['admin']['sortOptions'] + } | { // For `richText` fields richTextComponentMap?: Map @@ -90,36 +119,6 @@ export type FormFieldBase = { // For `upload` fields relationTo?: UploadField['relationTo'] } - | { - allowCreate?: RelationshipField['admin']['allowCreate'] - filterOptions?: RelationshipField['filterOptions'] - // For `relationship` fields - relationTo?: RelationshipField['relationTo'] - sortOptions?: RelationshipField['admin']['sortOptions'] - } - | { - blocks?: ReducedBlock[] - labels?: BlockField['labels'] - maxRows?: BlockField['maxRows'] - minRows?: BlockField['minRows'] - // For `blocks` fields - slug?: string - } - | { - hasMany?: boolean - max?: number - maxRows?: number - min?: number - // For `number` fields - step?: number - } - | { - label?: RowLabel - labels?: ArrayField['labels'] - maxRows?: ArrayField['maxRows'] - // For `array` fields - minRows?: ArrayField['minRows'] - } | { tabs?: MappedTab[] } diff --git a/packages/ui/src/forms/useField/index.tsx b/packages/ui/src/forms/useField/index.tsx index b0af1a79f..d322c1a20 100644 --- a/packages/ui/src/forms/useField/index.tsx +++ b/packages/ui/src/forms/useField/index.tsx @@ -41,6 +41,7 @@ export const useField = (options: Options): FieldType => { const { getData, getDataByPath, getSiblingData, setModified } = useForm() + const filterOptions = field?.filterOptions const value = field?.value as T const initialValue = field?.initialValue as T const valid = typeof field?.valid === 'boolean' ? field.valid : true @@ -79,6 +80,7 @@ export const useField = (options: Options): FieldType => { const result: FieldType = useMemo( () => ({ errorMessage: field?.errorMessage, + filterOptions, formProcessing: processing, formSubmitted: submitted, initialValue, @@ -106,6 +108,7 @@ export const useField = (options: Options): FieldType => { schemaPath, readOnly, permissions, + filterOptions, ], ) diff --git a/packages/ui/src/forms/useField/types.ts b/packages/ui/src/forms/useField/types.ts index f577598b9..bd7a53eff 100644 --- a/packages/ui/src/forms/useField/types.ts +++ b/packages/ui/src/forms/useField/types.ts @@ -1,4 +1,4 @@ -import type { ClientValidate, FieldPermissions, Row } from 'payload/types' +import type { ClientValidate, FieldPermissions, FilterOptionsResult, Row } from 'payload/types' export type Options = { disableFormData?: boolean @@ -12,6 +12,7 @@ export type Options = { export type FieldType = { errorMessage?: string + filterOptions?: FilterOptionsResult formProcessing: boolean formSubmitted: boolean initialValue?: T diff --git a/packages/ui/src/utilities/getFormState.ts b/packages/ui/src/utilities/getFormState.ts index c0556cbb8..ef73926d0 100644 --- a/packages/ui/src/utilities/getFormState.ts +++ b/packages/ui/src/utilities/getFormState.ts @@ -1,6 +1,6 @@ import type { FormState, SanitizedConfig } from 'payload/types' -import type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js' +import type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js' export const getFormState = async (args: { apiRoute: SanitizedConfig['routes']['api'] diff --git a/test/_community/collections/Posts/index.ts b/test/_community/collections/Posts/index.ts index 9d8bb26f0..96603ea00 100644 --- a/test/_community/collections/Posts/index.ts +++ b/test/_community/collections/Posts/index.ts @@ -21,6 +21,17 @@ export const PostsCollection: CollectionConfig = { { name: 'relationship', type: 'relationship', + filterOptions: ({ id }) => { + return { + where: [ + { + id: { + not_equals: id, + }, + }, + ], + } + }, relationTo: ['posts'], }, { diff --git a/tsconfig.json b/tsconfig.json index 0fa2bc28b..426caf83c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -88,7 +88,7 @@ "./packages/graphql/src" ], "@payload-config": [ - "./test/access-control/config.ts" + "./test/_community/config.ts" ] } },