guidance for payload.update() when updating Relationship field

default discord avatar
RobertAlbus
2 years ago
1 3

Hi Payload team. I haven't been able to find substantial documentation for payload.update() when the updated field is

  1. a relationship field
  2. hasMany: true
  3. relationshipTo: [ // more than one collection // ]

The gist: I have a parent-child relationship between items of various collections (mixed-content tree). I want to re-parent the updated item by removing the child from the old parent's child field and then add it to the new parent's child field.

I have a hook set up to do this, but I cannot for the life of me figure out what the call to payload.update(...) is expecting to receive.

If I manually add entries to the child field for an item in the admin GUI then I get back an array in the child field. This is sensible:

[
  {
    relationTo: 'pages',
    value: {
      parent: [Object],
      title: 'Parent Page',
      renderings: [],
      children: [],
      createdAt: '2021-06-20T06:04:33.461Z',
      updatedAt: '2021-06-22T01:08:52.412Z',
      id: '60ceda71361a3300191cfd4b'
    }
  },
  {
    relationTo: 'pages',
    value: {
      parent: [Object],
      title: 'Sub Page',
      renderings: [],
      children: [],
      createdAt: '2021-06-20T06:05:13.529Z',
      updatedAt: '2021-06-22T00:57:55.799Z',
      id: '60ceda99361a3300191cfd7b'
    }
  }
]

I would expect I can update this list by removing an item from the array or appending another object with the form

{
  relationTo: 'datasource-folders',
  value: {
    id: '60ceda71361a3300191caa12'
  }
}

however this will not save.

payload.update({
      collection: originalParent.type,
      id: originalParent.id,
      data: {
        children: updatedChildren // the array above
      }
    });

The error provided

Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters

makes it seem like Payload is expecting to receive data in the format

[
  ...
  {
    relationTo: 'datasource-folders',
    value: '60ceda71361a3300191caa12'
  },
]

however this is cursed. It doesn't work either and causes new craziness that encouraged me to drop the collection in Mongo and start fresh.


It would be great if you could point me to documentation for updating relationship fields, especially where hasMany: true and relationTo: [ // multiple collections // ].

If there isn't documentation but you're willing to explain it I would be happy to put in a PR for a documentation update.

TYSM

  • default discord avatar
    Ontopic
    2 years ago

    I feel you should look into hooks as quick advice. At least that's how I created something similar.

    1 reply
  • default discord avatar
    RobertAlbus
    2 years ago

    Yes thanks for the suggestion @Ontopic I am using hooks to implement this. Getting the hooks to fire was pretty straight forward, it's the interaction with the local API inside the hook where I am having no luck.

  • discord user avatar
    jmikrut
    Payload Team
    2 years ago

    Hey @RobertAlbus — I can shine some light on this for you!

    First up, from your question above, this snippet is an example for how a hasMany: true and relationTo: [ ] relationship will look when it is populated by the depth parameter:

    [
      {
        relationTo: 'pages',
        value: {
          parent: [Object],
          title: 'Sub Page',
          renderings: [],
          children: [],
          createdAt: '2021-06-20T06:05:13.529Z',
          updatedAt: '2021-06-22T00:57:55.799Z',
          id: '60ceda99361a3300191cfd7b'
        }
      }
    ]

    However, the actual data that is stored in the database looks like this:

    [
      {
        relationTo: 'pages',
        value: ObjectID('60ceda99361a3300191cfd7b'),
      }
    ]

    So, to update, you need to send it data just like you mentioned:

    [
      ...
      {
        relationTo: 'datasource-folders',
        value: '60ceda71361a3300191caa12'
      },
    ]

    What is the error that is displaying when you try to update with this data? From my perspective this looks exactly right!

    14 replies
    default discord avatar
    RobertAlbus
    2 years ago

    This is super helpful @jmikrut. Thanks for clarifying the expected shape for updating a relationship field with hasMany: true.

    The error I see when I submit an update

    const updatedChildren = [
      ...
      {
        relationTo: 'datasource-folders',
        value: '60ceda71361a3300191caa12'
      },
    ]; 
    // this array can also be empty
    
    payload.update({
          collection: originalParent.type,
          id: originalParent.id,
          data: {
            children: updatedChildren
          },
        });
    

    to either remove or add an item to the relationship field, the server crashes with the following stack trace

    app_1  | /node_modules/payload/dist/fields/relationshipPopulationPromise.js:19
    app_1  |                 idString = idString.toString();
    app_1  |                                     ^
    app_1  | 
    app_1  | TypeError: Cannot read property 'toString' of undefined
    app_1  |     at populate (/node_modules/payload/src/fields/relationshipPopulationPromise.ts:46:29)
    app_1  |     at rowPromise (/node_modules/payload/src/fields/relationshipPopulationPromise.ts:105:17)
    app_1  |     at /node_modules/payload/src/fields/relationshipPopulationPromise.ts:119:24
    app_1  |     at Array.forEach (<anonymous>)
    app_1  |     at /node_modules/payload/src/fields/relationshipPopulationPromise.ts:102:22
    app_1  |     at /node_modules/payload/src/fields/performFieldOperations.ts:118:86
    app_1  |     at Array.map (<anonymous>)
    app_1  |     at Payload.performFieldOperations (/node_modules/payload/src/fields/performFieldOperations.ts:118:66)
    app_1  |     at processTicksAndRejections (node:internal/process/task_queues:96:5)
    app_1  |     at Payload.update (/node_modules/payload/src/collections/operations/update.ts:272:12)
    

    After this the admin panel starts to behave weird. The page that displays the collection list for the recently-associated item does not show any items, except for every 3 or 4 page refreshes. The app throws the same stack trace as above on every page refresh for this collection's list view.

    I feel like I am just missing something basic but the stack trace, Payload source, documentation, and reviewing my code just aren't leading me where I need to go.

    discord user avatar
    jmikrut
    Payload Team
    2 years ago

    Hey @RobertAlbus — can you hook me up with your collection config?

    I will do some testing with it. Should be able to figure this out. Nothing jumps out at me as being incorrect with the code that you've provided thus far!

    discord user avatar
    jmikrut
    Payload Team
    2 years ago

    @RobertAlbus another thought... one thing I am thinking is that you might have some bad data somewhere in your database.

    The error that you sent would make sense if the shape of a document somewhere was bad. It's attempting to convert a Mongo ObjectID to a string and clearly failing. We will go ahead and add in a fix to that case to hopefully make everything a bit more stable for future users. But in the interim, I'd poke through your database for any documents affected by this update for an old or bad data shape.

    discord user avatar
    jmikrut
    Payload Team
    2 years ago

    @RobertAlbus I thought I'd just blow you up. You got me thinking.

    Just deployed 0.7.2 which safely accesses the toString method which was erroring out on what I imagine is potentially bad data. I'd be curious to see three things:

    1. Your config so I can test myself
    2. If this fixes your issue (I imagine it will "side-step" it)
    3. If you found any bad data in your DB
    default discord avatar
    RobertAlbus
    2 years ago
    1. my config is split up across a number for source files and some of it is also built dynamically. It might be easiest if I share a link to source code if you're good with that.
    2. this didn't fix the issue but the error changed
    3. I did find malformed data in the item I added the new relationship entry to:
      {
          "_id":"ObjectId(""60d3b796ad579d0018fe1b27"")",
          "title":"Kontralto Consulting Corp",
          "children":[
              {   // should have an ID but doesn't
                  "relationTo":"pages"
              }
          ],
          "createdAt":"ISODate(""2021-06-23T22:37:10.681Z"")",
          "updatedAt":"ISODate(""2021-06-23T22:39:25.322Z"")",
          "__v":0
      }
      
    default discord avatar
    Ontopic
    2 years ago

    I personally run into this every now and then, then I reseed my database. Not sure if that's an option for you, but otherwise fixes issues like this quite often.

    default discord avatar
    Ontopic
    2 years ago

    Thanks for sharing your repo, interesting research in there!

    default discord avatar
    RobertAlbus
    2 years ago

    then I reseed my database

    Thanks for the prompt, I blew away the database and tried again for sanity's sake. Sanity not found :P it happens with a fresh database without fail.

    default discord avatar
    Ontopic
    2 years ago

    I'm gonna be trying to get your repo to run and see what I can find. I personally only have experience with keeping one-to-one relationships in sync, which went fine, but one-to-many is definitely something I need to play around with anyway, for sanity's sake 🙃 Imagine what Payload could do with 39M...

    discord user avatar
    DanRibbens
    Payload Team
    2 years ago

    Hey @RobertAlbus,

    Thank you so much for sharing your repo, I like how you're building your collections and sharing functionality between them. This kind of smart achitecture is exactly the kinds of creative solutions we hoped people would do with Payload CMS.

    I found the bug! I noticed that the call to update the parent in with the children objects is being given the id of the parent and not the child document. Here is a screenshot of my debugger before the call to payload.update() in your hook:

    image (1)

    One important mention, You're using the beforeChange hook but new documents will not have an id yet. To do what you want, you'll need to change the hooks to run afterChange instead of beforeChange at least for new documents. Then you'll be able to use the correct child id for children relationships when you update the parent document.

    I hope this helps, let us know how it goes!

    default discord avatar
    RobertAlbus
    2 years ago

    Thanks for the response @DanRibbens. I see what you're pointing out there.

    My next steps were to move those hooks from beforeChange to afterChange. The issue was still present so I changed the hooks from field hooks to afterChange collection hooks but the issue of malformed data is still present.

    doc

    {
      id: '60dce0b06a737200188df539',
      parent: { relationTo: 'sites', value: '60dce0a56a737200188df506' },
      heading: 'My Page',
      renderings: [],
      children: [],
      createdAt: '2021-06-30T21:22:56.678Z',
      updatedAt: '2021-06-30T21:22:56.678Z'
    }
    

    parent

    {
      id: '60dce0a56a737200188df506',
      title: 'Kontralto Consulting Corp',
      children: [],
      createdAt: '2021-06-30T21:22:45.113Z',
      updatedAt: '2021-06-30T21:22:45.113Z'
    }
    

    updated children list

    [ { relationTo: 'pages', id: '60dce0b06a737200188df539' } ]
    

    api call

    payload.update({
      collection: 'sites',
      id: '60dce0a56a737200188df506',
      data: { children: [ { relationTo: 'pages', id: '60dce0b06a737200188df539' } ] }
    })
    

    database content

    > db.sites.find()
    { "_id" : ObjectId("60dce0a56a737200188df506"), "title" : "Kontralto Consulting Corp", "children" : [ { "relationTo" : "pages" } ], "createdAt" : ISODate("2021-06-30T21:22:45.113Z"), "updatedAt" : ISODate("2021-06-30T21:22:56.718Z"), "__v" : 0 }
    > db.pages.find()
    { "_id" : ObjectId("60dce0b06a737200188df539"), "parent" : { "relationTo" : "sites", "value" : ObjectId("60dce0a56a737200188df506") }, "heading" : "My Page", "renderings" : [ ], "children" : [ ], "createdAt" : ISODate("2021-06-30T21:22:56.678Z"), "updatedAt" : ISODate("2021-06-30T21:22:56.678Z"), "__v" : 0 }
    

    My apologies for continuing to bump this discussion - I really want to figure this out... I must just have something wrong.

    discord user avatar
    DanRibbens
    Payload Team
    2 years ago

    Hey @RobertAlbus, No problem!
    I just made a PR to your repo that fixes the bug, though you'll want to clean it up. The problem was the relationship object for the children was being sent as id instead of value. After making this change I was able to save a Page with a relationship to a Site without error and the database appears to be correct with both relationships being populated.

    https://github.com/RobertAlbus/kto-cms/pull/1

    We have a plan to update the documentation because I too was confused by this until I dug deeper. Beyond that we can work on adding validation so value is required for relationTo of multiple types. That would have caught this immediately.

    default discord avatar
    RobertAlbus
    2 years ago

    @DanRibbens ! This is fantastic. Thank you for digging into that. I checked out your branch and like what I see. Very instructive.

    I agree with updating the docs and adding validation. If you want to ping me for a review on either of those please feel free.

    discord user avatar
    DanRibbens
    Payload Team
    2 years ago

    Cool! Here is a PR for the documentation changes: #214

  • default discord avatar
    lrkcode
    9 months ago
Open the post
Continue the discussion in GitHub
Like what we're doing?
Star us on GitHub!

Star

Connect with the Payload Community on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.