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()`
This commit is contained in:
@@ -212,6 +212,7 @@ Functions can be written to make use of the following argument properties:
|
|||||||
|
|
||||||
- `user` - the authenticated user object
|
- `user` - the authenticated user object
|
||||||
- `locale` - the currently selected locale string
|
- `locale` - the currently selected locale string
|
||||||
|
- `req` - the `PayloadRequest` object
|
||||||
|
|
||||||
Here is an example of a `defaultValue` function:
|
Here is an example of a `defaultValue` function:
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ export const myField: Field = {
|
|||||||
name: 'attribution',
|
name: 'attribution',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
// highlight-start
|
// highlight-start
|
||||||
defaultValue: ({ user, locale }) =>
|
defaultValue: ({ user, locale, req }) =>
|
||||||
`${translation[locale]} ${user.name}`,
|
`${translation[locale]} ${user.name}`,
|
||||||
// highlight-end
|
// highlight-end
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,7 @@ export const myField: Field = {
|
|||||||
|
|
||||||
<Banner type="success">
|
<Banner type="success">
|
||||||
<strong>Tip:</strong>
|
<strong>Tip:</strong>
|
||||||
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`.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
### Validation
|
### Validation
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ import type {
|
|||||||
TextareaFieldValidation,
|
TextareaFieldValidation,
|
||||||
} from '../../index.js'
|
} from '../../index.js'
|
||||||
import type { DocumentPreferences } from '../../preferences/types.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 {
|
import type {
|
||||||
NumberFieldManyValidation,
|
NumberFieldManyValidation,
|
||||||
NumberFieldSingleValidation,
|
NumberFieldSingleValidation,
|
||||||
@@ -395,7 +395,7 @@ export interface FieldBase {
|
|||||||
admin?: Admin
|
admin?: Admin
|
||||||
/** Extension point to add your custom data. Server only. */
|
/** Extension point to add your custom data. Server only. */
|
||||||
custom?: Record<string, any>
|
custom?: Record<string, any>
|
||||||
defaultValue?: any
|
defaultValue?: DefaultValue
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
hooks?: {
|
hooks?: {
|
||||||
afterChange?: FieldHook[]
|
afterChange?: FieldHook[]
|
||||||
@@ -1338,7 +1338,7 @@ export type BlocksField = {
|
|||||||
isSortable?: boolean
|
isSortable?: boolean
|
||||||
} & Admin
|
} & Admin
|
||||||
blocks: Block[]
|
blocks: Block[]
|
||||||
defaultValue?: unknown
|
defaultValue?: DefaultValue
|
||||||
labels?: Labels
|
labels?: Labels
|
||||||
maxRows?: number
|
maxRows?: number
|
||||||
minRows?: number
|
minRows?: number
|
||||||
|
|||||||
@@ -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'
|
import { deepCopyObjectSimple } from '../utilities/deepCopyObject.js'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
defaultValue: ((args: any) => JsonValue) | any
|
defaultValue: DefaultValue
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
|
req: PayloadRequest
|
||||||
user: PayloadRequest['user']
|
user: PayloadRequest['user']
|
||||||
value?: JsonValue
|
value?: JsonValue
|
||||||
}
|
}
|
||||||
@@ -12,6 +13,7 @@ type Args = {
|
|||||||
export const getDefaultValue = async ({
|
export const getDefaultValue = async ({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
user,
|
user,
|
||||||
value,
|
value,
|
||||||
}: Args): Promise<JsonValue> => {
|
}: Args): Promise<JsonValue> => {
|
||||||
@@ -20,7 +22,7 @@ export const getDefaultValue = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue && typeof defaultValue === 'function') {
|
if (defaultValue && typeof defaultValue === 'function') {
|
||||||
return await defaultValue({ locale, user })
|
return await defaultValue({ locale, req, user })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof defaultValue === 'object') {
|
if (typeof defaultValue === 'object') {
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ export const promise = async ({
|
|||||||
siblingDoc[field.name] = await getDefaultValue({
|
siblingDoc[field.name] = await getDefaultValue({
|
||||||
defaultValue: field.defaultValue,
|
defaultValue: field.defaultValue,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
value: siblingDoc[field.name],
|
value: siblingDoc[field.name],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ export const promise = async <T>({
|
|||||||
siblingData[field.name] = await getDefaultValue({
|
siblingData[field.name] = await getDefaultValue({
|
||||||
defaultValue: field.defaultValue,
|
defaultValue: field.defaultValue,
|
||||||
locale: req.locale,
|
locale: req.locale,
|
||||||
|
req,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
value: siblingData[field.name],
|
value: siblingData[field.name],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -121,6 +121,15 @@ export type Where = {
|
|||||||
|
|
||||||
export type Sort = Array<string> | string
|
export type Sort = Array<string> | 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
|
* Applies pagination for join fields for including collection relationships
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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'
|
import { iterateFields } from './iterateFields.js'
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ type Args = {
|
|||||||
fields: FieldSchema[]
|
fields: FieldSchema[]
|
||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
|
req: PayloadRequest
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -16,6 +17,7 @@ export const calculateDefaultValues = async ({
|
|||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
user,
|
user,
|
||||||
}: Args): Promise<Data> => {
|
}: Args): Promise<Data> => {
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
@@ -23,6 +25,7 @@ export const calculateDefaultValues = async ({
|
|||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData: data,
|
siblingData: data,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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'
|
import { defaultValuePromise } from './promise.js'
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ type Args<T> = {
|
|||||||
fields: (Field | TabAsField)[]
|
fields: (Field | TabAsField)[]
|
||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
|
req: PayloadRequest
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -16,6 +17,7 @@ export const iterateFields = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}: Args<T>): Promise<void> => {
|
}: Args<T>): Promise<void> => {
|
||||||
@@ -28,6 +30,7 @@ export const iterateFields = async <T>({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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 { getDefaultValue } from 'payload'
|
||||||
import { fieldAffectsData, tabHasName } from 'payload/shared'
|
import { fieldAffectsData, tabHasName } from 'payload/shared'
|
||||||
@@ -10,6 +10,7 @@ type Args<T> = {
|
|||||||
field: Field | TabAsField
|
field: Field | TabAsField
|
||||||
id?: number | string
|
id?: number | string
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
|
req: PayloadRequest
|
||||||
siblingData: Data
|
siblingData: Data
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
@@ -20,6 +21,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
}: Args<T>): Promise<void> => {
|
}: Args<T>): Promise<void> => {
|
||||||
@@ -31,6 +33,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
siblingData[field.name] = await getDefaultValue({
|
siblingData[field.name] = await getDefaultValue({
|
||||||
defaultValue: field.defaultValue,
|
defaultValue: field.defaultValue,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
user,
|
user,
|
||||||
value: siblingData[field.name],
|
value: siblingData[field.name],
|
||||||
})
|
})
|
||||||
@@ -52,6 +55,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData: row,
|
siblingData: row,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
@@ -81,6 +85,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData: row,
|
siblingData: row,
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
@@ -94,13 +99,13 @@ export const defaultValuePromise = async <T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'collapsible':
|
case 'collapsible':
|
||||||
|
|
||||||
case 'row': {
|
case 'row': {
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -119,6 +124,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData: groupData,
|
siblingData: groupData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -143,6 +149,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData: tabSiblingData,
|
siblingData: tabSiblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
@@ -156,6 +163,7 @@ export const defaultValuePromise = async <T>({
|
|||||||
data,
|
data,
|
||||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||||
locale,
|
locale,
|
||||||
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise<FormState> =>
|
|||||||
data: dataWithDefaultValues,
|
data: dataWithDefaultValues,
|
||||||
fields,
|
fields,
|
||||||
locale: req.locale,
|
locale: req.locale,
|
||||||
|
req,
|
||||||
siblingData: dataWithDefaultValues,
|
siblingData: dataWithDefaultValues,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -151,6 +151,13 @@ const TextFields: CollectionConfig = {
|
|||||||
hasMany: true,
|
hasMany: true,
|
||||||
maxRows: 4,
|
maxRows: 4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'defaultValueFromReq',
|
||||||
|
type: 'text',
|
||||||
|
defaultValue: async ({ req }) => {
|
||||||
|
return Promise.resolve(req.context.defaultValue)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'disableListColumnText',
|
name: 'disableListColumnText',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
@@ -97,6 +97,20 @@ describe('Fields', () => {
|
|||||||
expect(fieldWithDefaultValue).toEqual(dependentOnFieldWithDefaultValue)
|
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 () => {
|
it('should localize an array of strings using hasMany', async () => {
|
||||||
const localizedHasMany = ['hello', 'world']
|
const localizedHasMany = ['hello', 'world']
|
||||||
const { id } = await payload.create({
|
const { id } = await payload.create({
|
||||||
|
|||||||
@@ -783,6 +783,7 @@ export interface TextField {
|
|||||||
localizedHasMany?: string[] | null;
|
localizedHasMany?: string[] | null;
|
||||||
withMinRows?: string[] | null;
|
withMinRows?: string[] | null;
|
||||||
withMaxRows?: string[] | null;
|
withMaxRows?: string[] | null;
|
||||||
|
defaultValueFromReq?: string | null;
|
||||||
disableListColumnText?: string | null;
|
disableListColumnText?: string | null;
|
||||||
disableListFilterText?: string | null;
|
disableListFilterText?: string | null;
|
||||||
array?:
|
array?:
|
||||||
@@ -2997,6 +2998,7 @@ export interface TextFieldsSelect<T extends boolean = true> {
|
|||||||
localizedHasMany?: T;
|
localizedHasMany?: T;
|
||||||
withMinRows?: T;
|
withMinRows?: T;
|
||||||
withMaxRows?: T;
|
withMaxRows?: T;
|
||||||
|
defaultValueFromReq?: T;
|
||||||
disableListColumnText?: T;
|
disableListColumnText?: T;
|
||||||
disableListFilterText?: T;
|
disableListFilterText?: T;
|
||||||
array?:
|
array?:
|
||||||
|
|||||||
Reference in New Issue
Block a user