I want to automatically populate the name field with the name of another collection which is related.
Take a look at the screenshot.
I want to populate the Name based on the name of the selected Range.
Here's my code:
import type { CollectionConfig } from 'payload/types'
export const Zones: CollectionConfig = {
slug: 'zones',
labels: {
singular: 'Zone',
plural: 'Zones',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'range',
type: 'relationship',
relationTo: 'ranges',
required: true,
},
{
name: 'priceType',
type: 'radio',
defaultValue: 'flat',
options: [
{
label: 'Flat Price',
value: 'flat',
},
{
label: 'Price per km over range start',
value: 'perkm',
},
],
admin: {
layout: 'horizontal',
width: '100%',
},
},
{
name: 'price',
label: 'Price (in NOK)',
type: 'text',
required: true,
defaultValue: '0',
validate: value => {
if (isNaN(value)) {
return 'Must be a number'
}
},
},
],
admin: {
useAsTitle: 'name',
group: 'Delivery',
defaultColumns: ['name', 'range', 'price', 'priceType', 'threshold'],
},
timestamps: true,
}
Is it that you want to give
name
a default value which is the name of the relationship, and then still be able to change it? Because if not, is it necessary to have a name field if the information can already be deduced from the title of the relationship. If you want to keep it to give your Zone a title, it could be done with a hidden field and a beforeChange hook to set this field value as the title of the relationship
Sorry im on m’y phone , you can se e I can access the field title of my document to fill the slug
I want the name field to be auto-populated with information from both the Range and price fields so that it is shown in the collection overview on the admin panel.
Example values for the name field:
-
0-21km - 200nok
-
21-50km - 50nok per km
Do I need a custom field though?
I'd like to just set the
name
text field's value to
range.name + price.value + 'nok'
whenever either the range or the price fields change value
I very much appreciate both of you helping me btw, so thank you!
Yep, you have to create this custom field, it's pretty easy if you already do some React
Alright 👍
I don't have much experience with React unfortunately.
Do you have a complete example by any chance?
You can try using a beforeChange hook on the collection (
https://payloadcms.com/docs/hooks/collections#beforechange). Whenever an entry is changed/created, it will modify the changed data to update the
name
value.
It would look a bit like that
import { CollectionBeforeChangeHook } from 'payload/types'
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data // incoming data to update or create with
}) => {
console.debug("beforeChangeHook data:", data); // Just for debugging, can be removed once you've confirmed it is working
return { ...data, name: data.range + data.price + 'nok' }; // Return data to update the document with
}
Because
range
is a relationship data.range will be the id of the selected Range. If you want to use its name instead, you will need to use the local api to fetch that info. It will probably be something like:
const range = payload.findByID({collection: "ranges", id: data.range})
You can then add the hook to your collection by doing:
export const Zones: CollectionConfig = {
slug: 'zones',
labels: {
singular: 'Zone',
plural: 'Zones',
},
hooks: { //
beforeChange: [ beforeChangeHook ] // These lines have been added
}, //
fields: [ ... ],
...
}
Wow, thank you very much!
You basically did the whole implementation for me too!
I'm gonna test it right away!
That worked perfectly!
Thank you for both the implementation and explanation
For those interested, here's the solution:
import payload from 'payload'
import type { CollectionConfig } from 'payload/types'
import { BeforeChangeHook } from 'payload/types'
const BeforeChangeHook: BeforeChangeHook = async ({
data, // incoming data to update or create with
}) => {
const range = await payload.findByID({ collection: 'ranges', id: data.range })
return { ...data, name: range.name + ' - ' + data.price + 'nok' }
}
export const Zones: CollectionConfig = {
slug: 'zones',
labels: {
singular: 'Zone',
plural: 'Zones',
},
hooks: {
beforeChange: [BeforeChangeHook],
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'range',
type: 'relationship',
relationTo: 'ranges',
required: true,
},
{
name: 'priceType',
type: 'radio',
defaultValue: 'flat',
options: [
{
label: 'Flat Price',
value: 'flat',
},
{
label: 'Price per km over range start',
value: 'perkm',
},
],
admin: {
layout: 'horizontal',
width: '100%',
},
},
{
name: 'price',
label: 'Price (in NOK)',
type: 'text',
required: true,
defaultValue: '0',
validate: value => {
if (isNaN(value)) {
return 'Must be a number'
}
},
},
],
admin: {
useAsTitle: 'name',
group: 'Delivery',
defaultColumns: ['name', 'range', 'price', 'priceType', 'threshold'],
},
timestamps: true,
}
Awesome, glad to hear 👍 btw you can probably set the name field to be hidden to avoid anyone changing it manually. If you still want the value to appear when you fetch it through the API, instead you can set condition to return false
Great! Hooks are very powerful. Thanks for sharing.
I did a custom field component as I wanted the data to change in real-time for a system I'm working on.
Here is the code if anybody is interested
import React, { useEffect, useState } from 'react';
import { Label, useFormFields, TextInput } from 'payload/components/forms';
import { Props } from 'payload/components/fields/Text';
export const TestField: React.FC<Props> = (props) => {
const { label, name } = props;
const [value, setValue] = useState<string>(); // you can use the useField hook instead
// get the values of the fields you want to use
const timeValue: any = useFormFields(([fields, dispatch]) => fields.time.value);
const distanceValue: any = useFormFields(([fields, dispatch]) => fields.distance.value);
const concatFunction = () => {
// you can do more stuff here
let message = `You haven't walked yet!`;
if (timeValue !== undefined && distanceValue !== undefined && timeValue > 0 && distanceValue > 0) {
message = `You walked ${distanceValue}m in ${timeValue}h`;
}
setValue(message);
}
useEffect(() => {
concatFunction()
}, [timeValue, distanceValue])
return (
<div>
<Label
label={label}
/>
<TextInput value={value !== undefined ? value : ''} name='uiField' path='uiField' onChange={concatFunction} />
</div>
)
}
You can plug it as a field of UI field type in your collection
Good job guys :payloadlove:
I tried doing that, but then the field didn't get set for some reason.
Seems like it might be a bug?
I tried to implement a hook the same way here:
https://github.com/Snailedlt/next-payload-gv/blob/90a8eb104882e1c8137f475083216ec3179773d0/payload/collections/PriceZones/hooks/beforeChange.ts#L11But I keep getting this error:
TypeError: hook is not a function
at eval (webpack-internal:///(api)/./node_modules/payload/dist/collections/operations/create.js:111:22)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async create (webpack-internal:///(api)/./node_modules/payload/dist/collections/operations/create.js:109:5)
at async handler (webpack-internal:///(api)/./node_modules/@payloadcms/next-payload/dist/handlers/[collection]/index.js:83:33)
Anyone know why I'm getting that error, and how to fix it?
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.