148 lines
5.0 KiB
Plaintext
148 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 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 the context in 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` syntax.
|
|
|
|
This is known as "type augmentation", a TypeScript feature which allows us to add types to existing types. Simply put this in any `.ts` or `.d.ts` file:
|
|
|
|
```ts
|
|
import { RequestContext as OriginalRequestContext } from 'payload'
|
|
|
|
declare module 'payload' {
|
|
// Create a new interface that merges your additional fields with the original one
|
|
export interface RequestContext extends OriginalRequestContext {
|
|
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 type augmentation can mess up your types if you do it wrong.
|