#Problem with afterChange Hook

1 messages · Page 1 of 1 (latest)

heavy gust
#

Hi everyone,

I'm experiencing a race condition issue with the afterChange hook. My setup is:

  • Payload CMS with afterChange hook
  • Hook triggers HTTP request to Express server
  • Express server rebuilds 11ty site
  • 11ty fetches updated data from Payload API

The problem: When 11ty requests the data, it receives the old/stale data, suggesting that afterChange fires before the actual data persistence.

Is this the expected behavior? Should I use afterOperation instead? Or is there a recommended pattern for triggering external rebuilds that depend on the updated data being available immediately?

Thank you so much in advance!

keen mason
#

What’s your afterChange hook look like

heavy gust
#

Sure, it looks like this:

import type { CollectionAfterChangeHook } from 'payload';
import dotenv from 'dotenv';
dotenv.config();

export const afterChangeHook: CollectionAfterChangeHook = async ({
    doc,
    operation,
    }) => {
        try {
            const rebuildURL = process.env.MANAGER_URL || ""
        
            const response = await fetch(rebuildURL, {
                method: 'POST',
                headers: {
                'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                  documentId: doc.id,
                }),
            });

            if (!response.ok) {
                console.error(`Rebuild Error: ${response.statusText}`)
            } else {
                console.log(`Rebuild succesful: ${await response.text()}`)
            }

        } catch (err) {
            console.error('Error in afterChangeHook:', err)
        }
    return doc
}

And in the collection I just do this:

import type { CollectionConfig } from 'payload'
import { afterChangeHook } from '@/hooks/afterChangeHook'

export const Extras: CollectionConfig = {
  slug: 'extras',
  admin: {

    useAsTitle: 'type',
  },
  hooks: {
        afterChange: [ afterChangeHook ],
  },
(...)
keen mason
#

Does the new data eventually show up?

heavy gust
#

No, the data doesn't eventually show up by itself. However, I notice there's a delay/lag effect: when I make changes and save (let's say update a text to "Test1" in a textfield), those changes don't appear in the rebuilt site. But if I make another change later (like "Test2") and save again, then the previous changes ("Test1") finally show up in the site. It's like the changes are always one step behind.

heavy gust
#

Found the solution! The issue was indeed related to database transactions. The afterChange hook executes within the active transaction, but the data hasn't been committed yet when the external rebuild process tries to read it.

To fix it, I just wrapped the fetch call in setImmediate() to execute it after the transaction commits:

import type { CollectionAfterChangeHook } from 'payload';
import dotenv from 'dotenv';
dotenv.config();

export const afterChangeHook: CollectionAfterChangeHook = async ({
    doc,
    operation,
}) => {
    setImmediate(async () => { // <---- this fixed it
        try {
            const rebuildURL = process.env.MANAGER_URL || ""
        
            const response = await fetch(rebuildURL, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    documentId: doc.id,
                }),
            });

            if (!response.ok) {
                console.error(`Rebuild Error: ${response.statusText}`)
            } else {
                console.log(`Rebuild successful: ${await response.text()}`)
            }

        } catch (err) {
            console.error('Error in afterChangeHook:', err)
        }
    });
    
    return doc;
}

I think this works because setImmediate() schedules execution for the next iteration of the Node.js event loop. At that point, all the payload operations should be finished, or at least the database transactions, and I think that is what is happening because it works now 😄 .

Thanks for your response @keen mason .