I have a collection (CategoryResults) and a global (Results). I am trying to read in the descriptions of my category results and use them as options in a select field.
I've tried a couple approaches so far, but haven't quite gotten the desired result.
screenshots of my code and console output are attached. also worth noting that i'm running on the beta version of payload
Two main questions:
1. Is this a feasible/intended approach for using hooks? I'm new to payload so i wonder if I'm even on the right track here. what i'm trying to do is pretty simple.
2. I previously tried using a relationship field to link the two entities, but was never able to access the items within the array. is this an intended use case for the relationship field?
anything helps, thanks!
What are ya building? It feels like you should have 2 collections. A global is a single doc, ie you would only ever have 1 result – if you want them to be per user, a global would not work. Again not sure what you’re building.
As for the select field, I think you’re trying to create a select field to have dynamic categories based on another collection? If so you’d want to create a custom component for the field. That react component would render a Select field and the options would be populated by querying against the category results collection. The config, would have a field type of “text” though instead of “select” – but visually you would be rendering a select component.
Here is an example, while this is V2, the idea is the same:
https://payloadcms.com/blog/how-to-create-a-custom-select-field-in-payload-a-step-by-step-guidethanks for the response! i actually found that article over the weekend.
I'm just trying to get familiar with Payload before I ramp onto a client project (i'm from Atomic Object, if you know us). I agree though that i should be using collections here.
you're also pretty spot on with my goal in regards to the dynamically populated select field. I've just been having a hard time wrapping my head around best practices and the intended usages of some fields, typing, and various hooks
Heyo, definitely familiar!
Hooks are just things that happen during the lifecycle of a CRUD operation on a doc. And we expose certain hooks so you can tie into that lifecycle as much or as little as you’d like.
For example, say you had an order/cart collection with some fields, when a cart or purchase converts you might want to send them an email related to their purchase. You would use an afterChange hook. (Imagine there is a status field, and when it changes to complete, you send the email)
Hooks are not meant to populate parts of the config though, which is what your example was going after. Select fields have static types generated, which means the options need to be deterministic at build time. Since you want a dynamic select field, based on another collection, the type can’t be static and that’s the reason you’d use a text field to save the data (since it’s not an enum) but display a select component on the frontend.
But this is just a glimpse into hooks, they are very very powerful once you understand where they fit in.
Another quick example would be, say users can fill out a username field (with unique: true), you might use a beforeValidate or beforeChange hook to lowercase the username so users can’t create similar jWill and JWill usernames.
As for questions fire away, I’ll do my best to answer!
Another one, for our website, we use afterChange hooks on posts and pages to tie into the NextJS on demand revalidation
awesome, thanks for these examples!
I'm also wondering about the ui field. is that just another way to render custom components?
i got the options working in my dropdown btw, thanks a ton
actually seems that i spoke too soon. i was successfully able to fetch data from a collection and use it in the dropdown.
when i was going to hook up the value selection, i made this a client component to implement the useFIeld hook, but now i'm seeing this error.
also what is the path and how am i supposed to access that here?
I believe path comes through as a prop, it might be field.path though. Console logging all props will probably be helpful in general as you build custom components just to see what they give you.
As for the compile error, you’re so close! But you can’t use React hooks in server components.
So fetch the options like you are (in a server component, ie without use client at the top). Then pass your dropdownOptions to a child client component (ie a file
withuse client at the top) in a different file.
You’ll need to thread the path through as well
You cannot use
payloadin a client component, and you cannot use hooks in a server component. That’s the short of it.
As for ui fields, they are not connected to the form state. No data is stored in a DB when using them. And in this case, you want to save the selected value so you’d use a text field as you are. ui fields can be useful for banners, and other things.
so i'm actually not seeing a path on the props
props: {
i18n: {
dateFNS: [Object],
dateFNSKey: 'en-US',
fallbackLanguage: 'en',
language: 'en',
t: [Function: t],
translations: [Object]
},
payload: BasePayload {
auth: [AsyncFunction: auth],
authStrategies: [Array],
collections: [Object],
config: [Object],
count: [AsyncFunction: count],
create: [AsyncFunction: create],
db: [Object],
decrypt: [Function: decrypt],
duplicate: [AsyncFunction: duplicate],
email: [Object],
encrypt: [Function: encrypt],
extensions: undefined,
find: [AsyncFunction: find],
findByID: [AsyncFunction: findByID],
findGlobal: [AsyncFunction: findGlobal],
findGlobalVersionByID: [AsyncFunction: findGlobalVersionByID],
findGlobalVersions: [AsyncFunction: findGlobalVersions],
findVersionByID: [AsyncFunction: findVersionByID],
findVersions: [AsyncFunction: findVersions],
forgotPassword: [AsyncFunction: forgotPassword],
getAdminURL: [Function: getAdminURL],
getAPIURL: [Function: getAPIURL],
globals: [Object],
importMap: [Object],
logger: [EventEmitter],
login: [AsyncFunction: login],
resetPassword: [AsyncFunction: resetPassword],
restoreGlobalVersion: [AsyncFunction: restoreGlobalVersion],
restoreVersion: [AsyncFunction: restoreVersion],
schema: undefined,
secret: '48fcc36822cbb8a6db0d865cd949176d',
sendEmail: [AsyncFunction: sendEmail],
types: undefined,
unlock: [AsyncFunction: unlock],
updateGlobal: [AsyncFunction: updateGlobal],
validationRules: undefined,
verifyEmail: [AsyncFunction: verifyEmail],
versions: {}
},
field: {
name: 'categoryResults',
type: 'text',
admin: [Object],
label: 'Category Results',
validate: [Function (anonymous)],
hooks: {},
access: {}
}
}the other advice makes a ton of sense though
should path be here?
Server components don’t receive path because dynamic fields are unknown at render time
We’re exploring ways around this, but this is the current behavior
gotcha. should i change my implementation of this custom select field then? i'm not sure what the best way to go about this might be honestly
I think you can use a hook called useFieldProps and that will expose path
awesome, using that hook in my client component worked!
thank you for being so helpful and responsive. this discord has been a great resource for me and my coworkers at atomic using payload on projects
another question for ya.
i'm now using this custom select in our application and want the options to change based on the value of a sibling field. in the server component, is there a way to access the siblingData? haven't been able to find anything in the docs or discord that aren't about the condition admin option (which seems to be about showing/hiding a field)
export const CustomSelectServerComponent: React.FC<SelectFieldClientProps> = async (props) => {
const payload = await getPayloadHMR({ config })
const movements = await payload.findByID({
id: '66e45dea65dc5d103adcaead', //this id needs to come in based on the value of a sibling field (relationship field for movements collection)
collection: 'movements',
depth: 2,
})
// console.log(props) does not contain sibling data
const dropdownOptions = movements?.resultsList.flatMap((doc) => ({
label: doc.name,
value: doc.id,
}))
return (
<div>
<CustomSelectClientComponent dropdownOptions={[{ label: 'test', value: 'test' }]} />
</div>
)
}
export default CustomSelectServerComponentthey would have to move this logic solely to the client right? Since they don’t get the doc id.
So basically your client component would need to read the sibling data and then fetch the options, instead of on the server
yeah that's what i was starting to think
unless i could do it at the collection/field hooks level
but i would need to use the rest api instead of the local api now correct? since we can't access the payload object from client components
Yeah that’s correct. With credentials: include to pass the user along via cookies
i have some refactoring to do, but got this working. does anything stand out? want to make sure i'm understanding the use cases of these hooks lol
'use client'
import { SelectInput, useField, useFieldProps, useFormFields } from '@payloadcms/ui'
import * as React from 'react'
interface CustomSelectProps {}
export const CustomSelectClientComponent: React.FC<CustomSelectProps> = () => {
const { path } = useFieldProps()
const { value, setValue } = useField({})
const [dropdownOptions, setDropdownOptions] = React.useState([])
//TODO: figure out how to use a variable
const testSelectionId = useFormFields(
([fields, dispatch]) => fields['movementPortion.0.groups.0.tests.0.test'],
)
React.useEffect(() => {
const fetchData = async () => {
try {
if (testSelectionId?.value !== undefined && testSelectionId?.value !== '') {
const res = await fetch(`http://localhost:3000/api/movements/${testSelectionId.value}`)
if (!res.ok) {
throw new Error('Network response was not ok')
}
const json = await res.json()
setDropdownOptions(
json.resultsList.flatMap((doc) => ({
label: doc.name,
value: doc.id,
})),
)
}
} catch (error) {
console.error('Error fetching data:', error)
}
}
fetchData()
}, [testSelectionId])
return (
<div>
<SelectInput
options={dropdownOptions}
label="Results"
name={path}
value={value}
onChange={(selectedOption) => {
if (selectedOption) {
setValue(selectedOption.value)
} else setValue(null)
}}
path={path}
/>
</div>
)
}
export default CustomSelectClientComponentYeah I think it looks pretty good 👍 for the TODO path, is it similar to the path of your custom component?
the TODO path is a sibling field, so it is somewhat similar yeah
Star
Discord
online
Get dedicated engineering support directly from the Payload team.