Skip to content

Commit

Permalink
feat: expose req to defaultValue function arguments (#9937)
Browse files Browse the repository at this point in the history
Rework of #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()`
  • Loading branch information
r1tsuu authored Dec 16, 2024
1 parent 26a10ed commit 6dea111
Show file tree
Hide file tree
Showing 13 changed files with 64 additions and 12 deletions.
5 changes: 3 additions & 2 deletions docs/fields/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -227,15 +228,15 @@ export const myField: Field = {
name: 'attribution',
type: 'text',
// highlight-start
defaultValue: ({ user, locale }) =>
defaultValue: ({ user, locale, req }) =>
`${translation[locale]} ${user.name}`,
// highlight-end
}
```

<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
Expand Down
6 changes: 3 additions & 3 deletions packages/payload/src/fields/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -1338,7 +1338,7 @@ export type BlocksField = {
isSortable?: boolean
} & Admin
blocks: Block[]
defaultValue?: unknown
defaultValue?: DefaultValue
labels?: Labels
maxRows?: number
minRows?: number
Expand Down
8 changes: 5 additions & 3 deletions packages/payload/src/fields/getDefaultValue.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
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
}

export const getDefaultValue = async ({
defaultValue,
locale,
req,
user,
value,
}: Args): Promise<JsonValue> => {
Expand All @@ -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') {
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/fields/hooks/afterRead/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
})
Expand Down
9 changes: 9 additions & 0 deletions packages/payload/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -7,6 +7,7 @@ type Args = {
fields: FieldSchema[]
id?: number | string
locale: string | undefined
req: PayloadRequest
siblingData: Data
user: User
}
Expand All @@ -16,13 +17,15 @@ export const calculateDefaultValues = async ({
data,
fields,
locale,
req,
user,
}: Args): Promise<Data> => {
await iterateFields({
id,
data,
fields,
locale,
req,
siblingData: data,
user,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -7,6 +7,7 @@ type Args<T> = {
fields: (Field | TabAsField)[]
id?: number | string
locale: string | undefined
req: PayloadRequest
siblingData: Data
user: User
}
Expand All @@ -16,6 +17,7 @@ export const iterateFields = async <T>({
data,
fields,
locale,
req,
siblingData,
user,
}: Args<T>): Promise<void> => {
Expand All @@ -28,6 +30,7 @@ export const iterateFields = async <T>({
data,
field,
locale,
req,
siblingData,
user,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -10,6 +10,7 @@ type Args<T> = {
field: Field | TabAsField
id?: number | string
locale: string | undefined
req: PayloadRequest
siblingData: Data
user: User
}
Expand All @@ -20,6 +21,7 @@ export const defaultValuePromise = async <T>({
data,
field,
locale,
req,
siblingData,
user,
}: Args<T>): Promise<void> => {
Expand All @@ -31,6 +33,7 @@ export const defaultValuePromise = async <T>({
siblingData[field.name] = await getDefaultValue({
defaultValue: field.defaultValue,
locale,
req,
user,
value: siblingData[field.name],
})
Expand All @@ -52,6 +55,7 @@ export const defaultValuePromise = async <T>({
data,
fields: field.fields,
locale,
req,
siblingData: row,
user,
}),
Expand Down Expand Up @@ -81,6 +85,7 @@ export const defaultValuePromise = async <T>({
data,
fields: block.fields,
locale,
req,
siblingData: row,
user,
}),
Expand All @@ -94,13 +99,13 @@ export const defaultValuePromise = async <T>({
}

case 'collapsible':

case 'row': {
await iterateFields({
id,
data,
fields: field.fields,
locale,
req,
siblingData,
user,
})
Expand All @@ -119,6 +124,7 @@ export const defaultValuePromise = async <T>({
data,
fields: field.fields,
locale,
req,
siblingData: groupData,
user,
})
Expand All @@ -143,6 +149,7 @@ export const defaultValuePromise = async <T>({
data,
fields: field.fields,
locale,
req,
siblingData: tabSiblingData,
user,
})
Expand All @@ -156,6 +163,7 @@ export const defaultValuePromise = async <T>({
data,
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
locale,
req,
siblingData,
user,
})
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/forms/fieldSchemasToFormState/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise<FormState> =>
data: dataWithDefaultValues,
fields,
locale: req.locale,
req,
siblingData: dataWithDefaultValues,
user: req.user,
})
Expand Down
7 changes: 7 additions & 0 deletions test/fields/collections/Text/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
14 changes: 14 additions & 0 deletions test/fields/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 2 additions & 0 deletions test/fields/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?:
Expand Down Expand Up @@ -2997,6 +2998,7 @@ export interface TextFieldsSelect<T extends boolean = true> {
localizedHasMany?: T;
withMinRows?: T;
withMaxRows?: T;
defaultValueFromReq?: T;
disableListColumnText?: T;
disableListFilterText?: T;
array?:
Expand Down

0 comments on commit 6dea111

Please sign in to comment.