Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Using collection data to populate another field

default discord avatar
_jkyrielast year
17

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-guide
  • default discord avatar
    _jkyrielast year
    @281120856527077378

    thanks 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

  • default discord avatar
    _jkyrielast year

    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

    with

    use client at the top) in a different file.



    You’ll need to thread the path through as well



    You cannot use

    payload

    in 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.

  • default discord avatar
    _jkyrielast year

    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

  • @808734492645785600

    should path be here?

  • discord user avatar
    jacobsfletch
    last year

    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

  • default discord avatar
    _jkyrielast year

    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

  • default discord avatar
    _jkyrielast year

    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



    @281120856527077378

    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 CustomSelectServerComponent
  • @808734492645785600

    they 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

  • default discord avatar
    _jkyrielast year

    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

  • default discord avatar
    _jkyrielast year

    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 CustomSelectClientComponent
  • Yeah I think it looks pretty good 👍 for the TODO path, is it similar to the path of your custom component?

  • default discord avatar
    _jkyrielast year

    the TODO path is a sibling field, so it is somewhat similar yeah

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.