The original documentation was unnecessarily complex - you don't need to import the original interface and extend from it in order to add additional properties to it via module augmentation.
146 lines
5.0 KiB
Plaintext
146 lines
5.0 KiB
Plaintext
---
|
|
title: Context
|
|
label: Context
|
|
order: 50
|
|
desc: Context allows you to pass in extra data that can be shared between hooks
|
|
keywords: hooks, context, payload context, payloadcontext, data, extra data, shared data, shared, extra
|
|
---
|
|
|
|
The `context` object is used to share data across different Hooks. This persists throughout the entire lifecycle of a request and is available within every Hook. By setting properties to `req.context`, you can effectively share logic across multiple Hooks.
|
|
|
|
## When To Use Context
|
|
|
|
Context gives you a way forward on otherwise difficult problems such as:
|
|
|
|
1. **Passing data between Hooks**: Needing data in multiple Hooks from a 3rd party API, it could be retrieved and used in `beforeChange` and later used again in an `afterChange` hook without having to fetch it twice.
|
|
2. **Preventing infinite loops**: Calling `payload.update()` on the same document that triggered an `afterChange` hook will create an infinite loop, control the flow by assigning a no-op condition to context
|
|
3. **Passing data to Local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
|
|
4. **Passing data between hooks and middleware or custom endpoints**: Hooks could set context across multiple collections and then be used in a final `postMiddleware`.
|
|
|
|
## How To Use Context
|
|
|
|
Let's see examples on how context can be used in the first two scenarios mentioned above:
|
|
|
|
### Passing Data Between Hooks
|
|
|
|
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it in the context of a later hook.
|
|
|
|
For example:
|
|
|
|
```ts
|
|
import type { CollectionConfig } from 'payload'
|
|
|
|
const Customer: CollectionConfig = {
|
|
slug: 'customers',
|
|
hooks: {
|
|
beforeChange: [
|
|
async ({ context, data }) => {
|
|
// assign the customerData to context for use later
|
|
context.customerData = await fetchCustomerData(data.customerID)
|
|
return {
|
|
...data,
|
|
// some data we use here
|
|
name: context.customerData.name,
|
|
}
|
|
},
|
|
],
|
|
afterChange: [
|
|
async ({ context, doc, req }) => {
|
|
// use context.customerData without needing to fetch it again
|
|
if (context.customerData.contacted === false) {
|
|
createTodo('Call Customer', context.customerData)
|
|
}
|
|
},
|
|
],
|
|
},
|
|
fields: [
|
|
/* ... */
|
|
],
|
|
}
|
|
```
|
|
|
|
### Preventing Infinite Loops
|
|
|
|
Let's say you have an `afterChange` hook, and you want to do a calculation inside the hook (as the document ID needed for the calculation is available in the `afterChange` hook, but not in the `beforeChange` hook). Once that's done, you want to update the document with the result of the calculation.
|
|
|
|
Bad example:
|
|
|
|
```ts
|
|
import type { CollectionConfig } from 'payload'
|
|
|
|
const Customer: CollectionConfig = {
|
|
slug: 'customers',
|
|
hooks: {
|
|
afterChange: [
|
|
async ({ doc, req }) => {
|
|
await req.payload.update({
|
|
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
|
|
collection: 'customers',
|
|
id: doc.id,
|
|
data: {
|
|
...(await fetchCustomerData(data.customerID)),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
},
|
|
fields: [
|
|
/* ... */
|
|
],
|
|
}
|
|
```
|
|
|
|
Instead of the above, we need to tell the `afterChange` hook to not run again if it performs the update (and thus not update itself again). We can solve that with context.
|
|
|
|
Fixed example:
|
|
|
|
```ts
|
|
import type { CollectionConfig } from 'payload'
|
|
|
|
const MyCollection: CollectionConfig = {
|
|
slug: 'slug',
|
|
hooks: {
|
|
afterChange: [
|
|
async ({ context, doc, req }) => {
|
|
// return if flag was previously set
|
|
if (context.triggerAfterChange === false) {
|
|
return
|
|
}
|
|
await req.payload.update({
|
|
collection: contextHooksSlug,
|
|
id: doc.id,
|
|
data: {
|
|
...(await fetchCustomerData(data.customerID)),
|
|
},
|
|
context: {
|
|
// set a flag to prevent from running again
|
|
triggerAfterChange: false,
|
|
},
|
|
})
|
|
},
|
|
],
|
|
},
|
|
fields: [
|
|
/* ... */
|
|
],
|
|
}
|
|
```
|
|
|
|
## TypeScript
|
|
|
|
The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare module` syntax.
|
|
|
|
This is known as [module augmentation / declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation), a TypeScript feature which allows us to add properties to existing types. Simply put this in any `.ts` or `.d.ts` file:
|
|
|
|
```ts
|
|
declare module 'payload' {
|
|
// Augment the RequestContext interface to include your custom properties
|
|
export interface RequestContext {
|
|
myObject?: string
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as module augmentation can mess up your types if you do it wrong.
|