Hello, I am trying to model an online store and for my Order Item I have a collection of Items and I wanted the final price to be calculated as the sum of all the items's prices. How do I do this? I have looked at the field hooks documentation but I am not being able to make it work...
Hey Xavier! Take a look at
useFormFields
, the doc example is pretty much exactly what you're trying to achieve (
https://payloadcms.com/docs/admin/hooks#useformfields). ๐
Oh actually maybe not, depending on what exactly you're trying to achieve and if you want a field to automatically updates in the Payload admin UI as you're filling out the Order fields, or if you're only using it as an API.
For the latter, I think using the collection hook
beforeChange
would probably be more suited. There you'll have access to all of your Order's data, so you'll be able to use all of your item's prices to calculate your final price, and return the data with that final price field.
Feel free to share more details as to what exactly you're trying to achieve and/or some of the code of what you've tried so far!
The problem rn is the items have prices and the orders have prices and we want to populate the order price with the sum of the items's prices, but don't know how to get the item's price from where we are
We are using this as an API
interesting! First of all, useFormFields is only for React components - don't use them in hooks.
The items should be already passed in that hook
So what happens in the PayloadUI is irrelavant in our case, as long as the order's price can't be manually changed and reflects the sum of the item's prices
Ah gotcha, in that case yeah I'd look at the collection hooks (like
beforeChange
) instead
https://payloadcms.com/docs/hooks/collections#beforechangeI think that is indeed what we need but the documentation isn't very helpful on this aspect. The example shows the hook as something apart from the file and we want to use the hook within the the Orders collection
It is part of the Orders collection! You can add a "hooks: {" field to your orders collection and use the hook there
The current data from your collection will be passed in that hook. From that data, you count all the prices for your items.
Then, in that hook, you return the same data, but with the sum of all prices added to the "price" field
(like this)
Thank you very much, I think this is working ๐ We made the change to support basic operation, now we just need to get it right with the item's prices ๐ I'll close the ticket as soon as we can make this work!
This thread is great! We do also have a "virtual fields" example which demonstrates this pretty abstractly,
https://github.com/payloadcms/payload/tree/master/examples/virtual-fields. There's a
totalPrice
field on the
events
collection that is pretty closely aligned with what you're describing here. This field is not saved to the database but is dynamically populated using an
afterRead
hook which calculates the total by adding up ticket prices.
Hello, I'm Xavier's colleague. We have tried to use a "beforeChange" hook with moderate success and we are close to what we want to achieve. However, we found another problem, when trying to fetch an "item" from our "Items" collection, it seems that it can only be fetched asynchronously, but for what we are trying to achieve we need this "item" before the "beforeChange" hook is finished, which doesn't happen since it is asynchronous.
Is there any way to guarantee that the response is received before the hook "beforeChange" ends? Our code is the following:
The code vanished ๐ฎ
sorry xD, the previous one was too cropped, I changed it to show a little more
Hmm so it looks like you're calling the getItem function in the beforeChange hook
But that function's input doesn't require anything from the beforeChange args, right?
It's setting the args.data.price to the result of that function
it does require a value from args, sorry, that image is hardcoded. Here is the actual way we use args for the getItem function:
Ah okay, one sec, ill read through
that is what we want, but we want that to happen before the hook ends
Doesn't beforeChange provide a reference to the original item?
Pre-change
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data, // incoming data to update or create with
req, // full express request
operation, // name of the operation ie. 'create', 'update'
originalDoc, // original document
}) => {
return data; // Return data to either create or update a document with
}
and then you assign it to the hook
hooks: { beforeChange: beforeChangeHook}
In addition
You may want to do beforeOperation
The beforeOperation Hook type can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
side-effects that run before an operation begins
so you would...
const beforeOperationHook: CollectionBeforeOperationHook = async ({
args, // Original arguments passed into the operation
'update', // name of the operation
}) => {
// Logic that will fire beforeChange
return args; // Return operation arguments as necessary
}
yes the data has the itemId that we want to use to fetch the item in the Items collection, unless I am misunderstanding what you asked
hooks: {
beforeOperation: [],
beforeChange: [myBeforeChangeFunction],
},
@Torneidou Can you try the same logic, but in the beforeOperation
like this?
close
sec
({
args, // Original arguments passed into the operation
operation, // name of the operation
}) => {
if (operation === 'update') {
}
return args; // Return operation arguments as necessary
}
I'm not 100% sure, but i think operation is a string
of the operation that you want to access before it runs
So I think if you put the getItem logic in the if statement there
It should run before the beforeChange hook is fired
Typically you would have a file that contains the before operation hook you want to use
so maybe you have a
hooks
folder
so something like this? it is hardcoded just so i can get it to work
and myCustomHook.ts
ahh okay
Yeah that looks promising
I think it gets auto-awaited too
hmm
maybe you do need the async operator
lets see if it errors
but we should definitely switch to a separate file down the line for that reason, thanks
the async elsewhere? It's already on the getItem function no?
ah you're right
sorry hehe
Any luck with the hook?
i like to throw in a bunch of logs too just to see whats going on
like id log operation, etc
sort of
now there is a weirder issue xD
Ooo what happened?
๐
hmm
it seems that the id is not recognized
but i remember this exact code working before
Isn't the id provided by args
yes, maybe i should try that way instead of hardcoding it for now
also you can do
const {price} = await getItem('fdwefewfewf')
assuming item isn't undefined
also @Torneidou I'm heading home from work soon, so when i get back i can help more ๐
thank you very much, i will keep posting more information but no pressure to respond
I look forward to it, im sure this will get figured out!
so that problem of no finding the resource was due to having written the wrong id ups (figures)
phew
๐
And do the ID's match up?
or the price
but unfortunately this approach doesn't seem to solve the original problem since the response of getting the item is printed after beforeChange is executed:
well
I've seen a couple of examples
And for instance
and unfortunately the "await" can't be used before calling getItem:
the async would go before the openeing parenthesis
async ({
operation,
args,
})
which makes me think it does need the async operator before the hook
omg
it worked
๐
YES
WOOT
im so happy you got it!
I gotta clock out and head home, but that's great news
putting the async here like you said made it possible to use await when calling the getItem function:
no red underline on "await"
๐
thank you very much for your time, have a great evening
our final solution for what we wanted to achieve was:
Heck yeah!
You did it!
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.