From 6dea111d28374b25c0ebc24f3e3377f73986736e Mon Sep 17 00:00:00 2001
From: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date: Mon, 16 Dec 2024 20:18:11 +0200
Subject: [PATCH] feat: expose `req` to `defaultValue` function arguments
(#9937)
Rework of https://github.com/payloadcms/payload/pull/5912
### What?
Now, when `defaultValue` is defined as function you can receive the
`req` argument:
```ts
{
name: 'defaultValueFromReq',
type: 'text',
defaultValue: async ({ req, user, locale }) => {
return Promise.resolve(req.context.defaultValue)
},
},
```
`user` and `locale` even though are repeated in `req`, this potentially
leaves some room to add more args in the future without removing them
now.
This also improves type for `defaultValue`:
```ts
type SerializableValue = boolean | number | object | string
export type DefaultValue =
| ((args: {
locale?: TypedLocale
req: PayloadRequest
user: PayloadRequest['user']
}) => SerializableValue)
| SerializableValue
```
### Why?
To access the current URL / search params / Local API and other things
directly in `defaultValue`.
### How?
Passes `req` through everywhere where we call `defaultValue()`
---
docs/fields/overview.mdx | 5 +++--
packages/payload/src/fields/config/types.ts | 6 +++---
packages/payload/src/fields/getDefaultValue.ts | 8 +++++---
.../payload/src/fields/hooks/afterRead/promise.ts | 1 +
.../src/fields/hooks/beforeValidate/promise.ts | 1 +
packages/payload/src/types/index.ts | 9 +++++++++
.../calculateDefaultValues/index.ts | 5 ++++-
.../calculateDefaultValues/iterateFields.ts | 5 ++++-
.../calculateDefaultValues/promise.ts | 12 ++++++++++--
.../ui/src/forms/fieldSchemasToFormState/index.tsx | 1 +
test/fields/collections/Text/index.ts | 7 +++++++
test/fields/int.spec.ts | 14 ++++++++++++++
test/fields/payload-types.ts | 2 ++
13 files changed, 64 insertions(+), 12 deletions(-)
diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx
index 09352b0deb3..94687f51d1a 100644
--- a/docs/fields/overview.mdx
+++ b/docs/fields/overview.mdx
@@ -212,6 +212,7 @@ Functions can be written to make use of the following argument properties:
- `user` - the authenticated user object
- `locale` - the currently selected locale string
+- `req` - the `PayloadRequest` object
Here is an example of a `defaultValue` function:
@@ -227,7 +228,7 @@ export const myField: Field = {
name: 'attribution',
type: 'text',
// highlight-start
- defaultValue: ({ user, locale }) =>
+ defaultValue: ({ user, locale, req }) =>
`${translation[locale]} ${user.name}`,
// highlight-end
}
@@ -235,7 +236,7 @@ export const myField: Field = {
Tip:
- You can use async `defaultValue` functions to fill fields with data from API requests.
+ You can use async `defaultValue` functions to fill fields with data from API requests or Local API using `req.payload`.
### Validation
diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts
index 565eaeb6675..8eff3f25f2d 100644
--- a/packages/payload/src/fields/config/types.ts
+++ b/packages/payload/src/fields/config/types.ts
@@ -127,7 +127,7 @@ import type {
TextareaFieldValidation,
} from '../../index.js'
import type { DocumentPreferences } from '../../preferences/types.js'
-import type { Operation, PayloadRequest, Where } from '../../types/index.js'
+import type { DefaultValue, Operation, PayloadRequest, Where } from '../../types/index.js'
import type {
NumberFieldManyValidation,
NumberFieldSingleValidation,
@@ -395,7 +395,7 @@ export interface FieldBase {
admin?: Admin
/** Extension point to add your custom data. Server only. */
custom?: Record
- defaultValue?: any
+ defaultValue?: DefaultValue
hidden?: boolean
hooks?: {
afterChange?: FieldHook[]
@@ -1338,7 +1338,7 @@ export type BlocksField = {
isSortable?: boolean
} & Admin
blocks: Block[]
- defaultValue?: unknown
+ defaultValue?: DefaultValue
labels?: Labels
maxRows?: number
minRows?: number
diff --git a/packages/payload/src/fields/getDefaultValue.ts b/packages/payload/src/fields/getDefaultValue.ts
index 583945aa2db..6b0d4a8e82f 100644
--- a/packages/payload/src/fields/getDefaultValue.ts
+++ b/packages/payload/src/fields/getDefaultValue.ts
@@ -1,10 +1,11 @@
-import type { JsonValue, PayloadRequest } from '../types/index.js'
+import type { DefaultValue, JsonValue, PayloadRequest } from '../types/index.js'
import { deepCopyObjectSimple } from '../utilities/deepCopyObject.js'
type Args = {
- defaultValue: ((args: any) => JsonValue) | any
+ defaultValue: DefaultValue
locale: string | undefined
+ req: PayloadRequest
user: PayloadRequest['user']
value?: JsonValue
}
@@ -12,6 +13,7 @@ type Args = {
export const getDefaultValue = async ({
defaultValue,
locale,
+ req,
user,
value,
}: Args): Promise => {
@@ -20,7 +22,7 @@ export const getDefaultValue = async ({
}
if (defaultValue && typeof defaultValue === 'function') {
- return await defaultValue({ locale, user })
+ return await defaultValue({ locale, req, user })
}
if (typeof defaultValue === 'object') {
diff --git a/packages/payload/src/fields/hooks/afterRead/promise.ts b/packages/payload/src/fields/hooks/afterRead/promise.ts
index 00fd083dbf3..509160a4df9 100644
--- a/packages/payload/src/fields/hooks/afterRead/promise.ts
+++ b/packages/payload/src/fields/hooks/afterRead/promise.ts
@@ -322,6 +322,7 @@ export const promise = async ({
siblingDoc[field.name] = await getDefaultValue({
defaultValue: field.defaultValue,
locale,
+ req,
user: req.user,
value: siblingDoc[field.name],
})
diff --git a/packages/payload/src/fields/hooks/beforeValidate/promise.ts b/packages/payload/src/fields/hooks/beforeValidate/promise.ts
index 68e19c0584d..6334234fb9c 100644
--- a/packages/payload/src/fields/hooks/beforeValidate/promise.ts
+++ b/packages/payload/src/fields/hooks/beforeValidate/promise.ts
@@ -316,6 +316,7 @@ export const promise = async ({
siblingData[field.name] = await getDefaultValue({
defaultValue: field.defaultValue,
locale: req.locale,
+ req,
user: req.user,
value: siblingData[field.name],
})
diff --git a/packages/payload/src/types/index.ts b/packages/payload/src/types/index.ts
index 5c88ee67cda..1b52efb661a 100644
--- a/packages/payload/src/types/index.ts
+++ b/packages/payload/src/types/index.ts
@@ -121,6 +121,15 @@ export type Where = {
export type Sort = Array | string
+type SerializableValue = boolean | number | object | string
+export type DefaultValue =
+ | ((args: {
+ locale?: TypedLocale
+ req: PayloadRequest
+ user: PayloadRequest['user']
+ }) => SerializableValue)
+ | SerializableValue
+
/**
* Applies pagination for join fields for including collection relationships
*/
diff --git a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/index.ts b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/index.ts
index 2f4e67436ae..a9287deda29 100644
--- a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/index.ts
+++ b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/index.ts
@@ -1,4 +1,4 @@
-import type { Data, Field as FieldSchema, User } from 'payload'
+import type { Data, Field as FieldSchema, PayloadRequest, User } from 'payload'
import { iterateFields } from './iterateFields.js'
@@ -7,6 +7,7 @@ type Args = {
fields: FieldSchema[]
id?: number | string
locale: string | undefined
+ req: PayloadRequest
siblingData: Data
user: User
}
@@ -16,6 +17,7 @@ export const calculateDefaultValues = async ({
data,
fields,
locale,
+ req,
user,
}: Args): Promise => {
await iterateFields({
@@ -23,6 +25,7 @@ export const calculateDefaultValues = async ({
data,
fields,
locale,
+ req,
siblingData: data,
user,
})
diff --git a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/iterateFields.ts b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/iterateFields.ts
index 7cb52a4f641..91505a593e9 100644
--- a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/iterateFields.ts
+++ b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/iterateFields.ts
@@ -1,4 +1,4 @@
-import type { Data, Field, TabAsField, User } from 'payload'
+import type { Data, Field, PayloadRequest, TabAsField, User } from 'payload'
import { defaultValuePromise } from './promise.js'
@@ -7,6 +7,7 @@ type Args = {
fields: (Field | TabAsField)[]
id?: number | string
locale: string | undefined
+ req: PayloadRequest
siblingData: Data
user: User
}
@@ -16,6 +17,7 @@ export const iterateFields = async ({
data,
fields,
locale,
+ req,
siblingData,
user,
}: Args): Promise => {
@@ -28,6 +30,7 @@ export const iterateFields = async ({
data,
field,
locale,
+ req,
siblingData,
user,
}),
diff --git a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/promise.ts b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/promise.ts
index e9637a5f10b..6d5aafbe63a 100644
--- a/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/promise.ts
+++ b/packages/ui/src/forms/fieldSchemasToFormState/calculateDefaultValues/promise.ts
@@ -1,4 +1,4 @@
-import type { Data, Field, TabAsField, User } from 'payload'
+import type { Data, Field, PayloadRequest, TabAsField, User } from 'payload'
import { getDefaultValue } from 'payload'
import { fieldAffectsData, tabHasName } from 'payload/shared'
@@ -10,6 +10,7 @@ type Args = {
field: Field | TabAsField
id?: number | string
locale: string | undefined
+ req: PayloadRequest
siblingData: Data
user: User
}
@@ -20,6 +21,7 @@ export const defaultValuePromise = async ({
data,
field,
locale,
+ req,
siblingData,
user,
}: Args): Promise => {
@@ -31,6 +33,7 @@ export const defaultValuePromise = async ({
siblingData[field.name] = await getDefaultValue({
defaultValue: field.defaultValue,
locale,
+ req,
user,
value: siblingData[field.name],
})
@@ -52,6 +55,7 @@ export const defaultValuePromise = async ({
data,
fields: field.fields,
locale,
+ req,
siblingData: row,
user,
}),
@@ -81,6 +85,7 @@ export const defaultValuePromise = async ({
data,
fields: block.fields,
locale,
+ req,
siblingData: row,
user,
}),
@@ -94,13 +99,13 @@ export const defaultValuePromise = async ({
}
case 'collapsible':
-
case 'row': {
await iterateFields({
id,
data,
fields: field.fields,
locale,
+ req,
siblingData,
user,
})
@@ -119,6 +124,7 @@ export const defaultValuePromise = async ({
data,
fields: field.fields,
locale,
+ req,
siblingData: groupData,
user,
})
@@ -143,6 +149,7 @@ export const defaultValuePromise = async ({
data,
fields: field.fields,
locale,
+ req,
siblingData: tabSiblingData,
user,
})
@@ -156,6 +163,7 @@ export const defaultValuePromise = async ({
data,
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
locale,
+ req,
siblingData,
user,
})
diff --git a/packages/ui/src/forms/fieldSchemasToFormState/index.tsx b/packages/ui/src/forms/fieldSchemasToFormState/index.tsx
index f1a83694024..15fe1acc12c 100644
--- a/packages/ui/src/forms/fieldSchemasToFormState/index.tsx
+++ b/packages/ui/src/forms/fieldSchemasToFormState/index.tsx
@@ -84,6 +84,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise =>
data: dataWithDefaultValues,
fields,
locale: req.locale,
+ req,
siblingData: dataWithDefaultValues,
user: req.user,
})
diff --git a/test/fields/collections/Text/index.ts b/test/fields/collections/Text/index.ts
index 58cfa252719..4f3fcf4e896 100644
--- a/test/fields/collections/Text/index.ts
+++ b/test/fields/collections/Text/index.ts
@@ -151,6 +151,13 @@ const TextFields: CollectionConfig = {
hasMany: true,
maxRows: 4,
},
+ {
+ name: 'defaultValueFromReq',
+ type: 'text',
+ defaultValue: async ({ req }) => {
+ return Promise.resolve(req.context.defaultValue)
+ },
+ },
{
name: 'disableListColumnText',
type: 'text',
diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts
index 9f349a33faf..b2fedd36565 100644
--- a/test/fields/int.spec.ts
+++ b/test/fields/int.spec.ts
@@ -97,6 +97,20 @@ describe('Fields', () => {
expect(fieldWithDefaultValue).toEqual(dependentOnFieldWithDefaultValue)
})
+ it('should populate function default values from req', async () => {
+ const text = await payload.create({
+ req: {
+ context: {
+ defaultValue: 'from-context',
+ },
+ },
+ collection: 'text-fields',
+ data: { text: 'required' },
+ })
+
+ expect(text.defaultValueFromReq).toBe('from-context')
+ })
+
it('should localize an array of strings using hasMany', async () => {
const localizedHasMany = ['hello', 'world']
const { id } = await payload.create({
diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts
index ccaf7608351..40cd06ddafb 100644
--- a/test/fields/payload-types.ts
+++ b/test/fields/payload-types.ts
@@ -783,6 +783,7 @@ export interface TextField {
localizedHasMany?: string[] | null;
withMinRows?: string[] | null;
withMaxRows?: string[] | null;
+ defaultValueFromReq?: string | null;
disableListColumnText?: string | null;
disableListFilterText?: string | null;
array?:
@@ -2997,6 +2998,7 @@ export interface TextFieldsSelect {
localizedHasMany?: T;
withMinRows?: T;
withMaxRows?: T;
+ defaultValueFromReq?: T;
disableListColumnText?: T;
disableListFilterText?: T;
array?: