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
|
||||
- `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 = {
|
||||
|
||||
<Banner type="success">
|
||||
<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>
|
||||
|
||||
### Validation
|
||||
|
||||
@@ -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<string, any>
|
||||
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
|
||||
|
||||
@@ -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<JsonValue> => {
|
||||
@@ -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') {
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -316,6 +316,7 @@ export const promise = async <T>({
|
||||
siblingData[field.name] = await getDefaultValue({
|
||||
defaultValue: field.defaultValue,
|
||||
locale: req.locale,
|
||||
req,
|
||||
user: req.user,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
@@ -121,6 +121,15 @@ export type Where = {
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -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<Data> => {
|
||||
await iterateFields({
|
||||
@@ -23,6 +25,7 @@ export const calculateDefaultValues = async ({
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData: data,
|
||||
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'
|
||||
|
||||
@@ -7,6 +7,7 @@ type Args<T> = {
|
||||
fields: (Field | TabAsField)[]
|
||||
id?: number | string
|
||||
locale: string | undefined
|
||||
req: PayloadRequest
|
||||
siblingData: Data
|
||||
user: User
|
||||
}
|
||||
@@ -16,6 +17,7 @@ export const iterateFields = async <T>({
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData,
|
||||
user,
|
||||
}: Args<T>): Promise<void> => {
|
||||
@@ -28,6 +30,7 @@ export const iterateFields = async <T>({
|
||||
data,
|
||||
field,
|
||||
locale,
|
||||
req,
|
||||
siblingData,
|
||||
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 { fieldAffectsData, tabHasName } from 'payload/shared'
|
||||
@@ -10,6 +10,7 @@ type Args<T> = {
|
||||
field: Field | TabAsField
|
||||
id?: number | string
|
||||
locale: string | undefined
|
||||
req: PayloadRequest
|
||||
siblingData: Data
|
||||
user: User
|
||||
}
|
||||
@@ -20,6 +21,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
field,
|
||||
locale,
|
||||
req,
|
||||
siblingData,
|
||||
user,
|
||||
}: Args<T>): Promise<void> => {
|
||||
@@ -31,6 +33,7 @@ export const defaultValuePromise = async <T>({
|
||||
siblingData[field.name] = await getDefaultValue({
|
||||
defaultValue: field.defaultValue,
|
||||
locale,
|
||||
req,
|
||||
user,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
@@ -52,6 +55,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData: row,
|
||||
user,
|
||||
}),
|
||||
@@ -81,6 +85,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
fields: block.fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData: row,
|
||||
user,
|
||||
}),
|
||||
@@ -94,13 +99,13 @@ export const defaultValuePromise = async <T>({
|
||||
}
|
||||
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
await iterateFields({
|
||||
id,
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData,
|
||||
user,
|
||||
})
|
||||
@@ -119,6 +124,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData: groupData,
|
||||
user,
|
||||
})
|
||||
@@ -143,6 +149,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
req,
|
||||
siblingData: tabSiblingData,
|
||||
user,
|
||||
})
|
||||
@@ -156,6 +163,7 @@ export const defaultValuePromise = async <T>({
|
||||
data,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
locale,
|
||||
req,
|
||||
siblingData,
|
||||
user,
|
||||
})
|
||||
|
||||
@@ -84,6 +84,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise<FormState> =>
|
||||
data: dataWithDefaultValues,
|
||||
fields,
|
||||
locale: req.locale,
|
||||
req,
|
||||
siblingData: dataWithDefaultValues,
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<T extends boolean = true> {
|
||||
localizedHasMany?: T;
|
||||
withMinRows?: T;
|
||||
withMaxRows?: T;
|
||||
defaultValueFromReq?: T;
|
||||
disableListColumnText?: T;
|
||||
disableListFilterText?: T;
|
||||
array?:
|
||||
|
||||
Reference in New Issue
Block a user