How to Filter out Existing References & Add a Default Reference in Schema

22 replies
Last updated: Sep 27, 2021
2 questions regarding an array of references:
1. Is there an easy way to filter out any already references already added to the parent array from the list of available array references in the default dialog dropdown? Should this potentially be default behavior (or a setting)?
2. I'm trying to reference a default author for my post document type, but since its an array of a reference, I'm not quite sure how to nest it.
This is my current attempt that is not working:

// post.js
export default {
...
  {
      name: 'authors',
      title: 'Authors',
      type: 'array',
      initialValue: {
        _type: 'reference',
        to: {
          _type: 'person',
          _ref: params.'9122a1cc-40d0-4120-9f30-3e997007896c'
        }
      },
  },
...
}
Sep 27, 2021, 5:42 PM
For question 1, you'll want to use the
unique()
validation rule. There's also the
filter
option, which in your case would look something like this:
options: {
  filter: ({ document }) => {
    const existingAuthors = document.authors.map(author => author._ref).filter(Boolean)
    return {
      filter: '!(_id in $existingAuthors)',
      params: {
        existingAuthors
      }
    }
  }
}
There's more on setting up dynamic filters
here .
With your initial value, is it a
parameterized template or just a static predefined author reference you're looking to use?
Sep 27, 2021, 5:55 PM
Oh, and that filter option goes on your authors array, sorry didn't make that clear in the original method.
Sep 27, 2021, 5:56 PM
No worries, I thought that made sense.
Regarding the
Filtering selected elements out of the reference array:I've added the validation rule and the options with some changes based on the array object, although the filter doesn't seem to be working correctly (the validation rule is though).

Expected state: What I'd expect to see in this case is that once I've added one project to my array of references that its no longer available in the selection options.
Observable state: projects that have been previously added are still visible among list of options, and return an error once added again

Here's this document's code:
//recommendation.js

export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
  ...
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: {type: 'sampleProject'}
        }
      ],
      validation: Rule => Rule.unique(),
      options: {
        filter: ({document}) => {
          const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
          return {
            fitlers: '!(_id in $existingprojects)',
            params: {
              existingProjects
            }
          }
        }
      }
    }
...
}
Do I need to change the document filter to specifically reference the projects array?
Sep 27, 2021, 6:28 PM
Regarding the set an initial value of a referenced author array, I don't think its a parameterized template. I just want to reference a single person among my person's document as the default author, with no changes to the persons document itself, and no new persons added as initial values as well
Sep 27, 2021, 6:31 PM
Ah, yes the validation option won't remove the option from the list, just throw an error when you do select it. That's why sometimes I prefer to just filter the list so that it's not even an option. And I was wrong when I said the filter goes on your authors array πŸ˜… . Blame it on it being Monday morning. It goes on your reference inside the array!Also, you had
fitlers
and
$exsitingprojects
, so once I changed them to
filter
and
$existingProjects
, plus corrected my misleading placement I got it to work!
export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingProjects)',
                params: {
                  existingProjects
                }
              }
            }
          }
        }
      ],
      
    }
  ]
}
Sep 27, 2021, 6:49 PM
Ah, yes the validation option won't remove the option from the list, just throw an error when you do select it. That's why sometimes I prefer to just filter the list so that it's not even an option. And I was wrong when I said the filter goes on your authors array πŸ˜… . Blame it on it being Monday morning. It goes on your reference inside the array!Also, you had
fitlers
and
$exsitingprojects
, so once I changed them to
filter
and
$existingProjects
, plus corrected my misleading placement I got it to work!
export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingProjects)',
                params: {
                  existingProjects
                }
              }
            }
          }
        }
      ],
      
    }
  ]
}
Sep 27, 2021, 6:49 PM
With the initial value: if it's not parameterized, you'll need to tell it the
_type
and
_ref
:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'author' }
      ],
      initialValue: [
        {
          _type: 'author',
          _ref: '<id-of-author-doc>'
        }
      ]
    },
This will only work on new documents, though. If you'd like to add it to previously created docs, you would have to mutate them via the Mutations API.
Sep 27, 2021, 6:59 PM
With the initial value: if it's not parameterized, you'll need to tell it the
_type
and
_ref
:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'author' }
      ],
      initialValue: [
        {
          _type: 'author',
          _ref: '<id-of-author-doc>'
        }
      ]
    },
This will only work on new documents, though. If you'd like to add it to previously created docs, you would have to mutate them via the Mutations API.
Sep 27, 2021, 6:59 PM
user M
the array reference filter works as expected! πŸ™Œ Do you have any recommendation for how I could move it into a separate
utils
area of sanity and re-use that across other of my references to keep my other references DRY with the same functionality? Or is this more appropriate to build a custom Sanity Reference component?
Also, big lol on
fitlers
πŸ€¦β€β™‚οΈ I totally didn't catch it until re-reading it 3 times so no worries.

Regarding the initial value reference in an array, I see your code example includes author as if its an object. Here's what my code looks like today
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          type: 'reference', to: {type: 'person'}
        }
      ],
      initialValue: {
        _type: 'reference',
        to: {

          _type: 'person',
          _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
        }
      },
    },
Should I instead add the initialValue to the reference in the array itself?

My ideal use case is that an editor already has a preassigned value in the array of that specific person reference, but can add additional and/or remove that one easily
Sep 27, 2021, 7:14 PM
I'll have to play with extracting the filter into a separate util, but that's a good idea. Especially as you start building custom Structures you'll find yourself repeating the same basic syntax in many places.
Sep 27, 2021, 7:20 PM
user M
so moving this initial value works in the sense that I still have to click the "add" button but then it autocompletes to my author everytime, which is not the behavior I'm looking for. Ideally, with the creation of each new post object, the author is already prefilled without clicking add, and then initialValue is left empty within the reference element so I don't get this behavior.
This is the code producing this behavior:

{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          Title: 'Author',
          type: 'reference',
          to: {type: 'person'},
          initialValue: {
            _type: 'person',
            _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
          }
        }
      ]
    },
Sep 27, 2021, 7:22 PM
user M
so moving this initial value works in the sense that I still have to click the "add" button but then it autocompletes to my author everytime, which is not the behavior I'm looking for. Ideally, with the creation of each new post object, the author is already prefilled without clicking add, and then initialValue is left empty within the reference element so I don't get this behavior.
This is the code producing this behavior:

{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          Title: 'Author',
          type: 'reference',
          to: {type: 'person'},
          initialValue: {
            _type: 'person',
            _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
          }
        }
      ]
    },
Sep 27, 2021, 7:22 PM
Ah, you're right, it would have to be something like this:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'reference',
          to: [
            { type: 'person' }
          ]
        }
      ],
      initialValue: [
        {
          _ref: '<id-of-person-doc>'
        }
      ]
    },
Then, when you create a new document it should automatically add it without having to click anything.
Sep 27, 2021, 7:29 PM
Beautiful! Tested and working on Localhost! Big Thanks for all the help this Monday morning/afternoon (PST)
user M
!
Sep 27, 2021, 7:37 PM
Beautiful! Tested and working on Localhost! Big Thanks for all the help this Monday morning/afternoon (PST)
user M
!
Sep 27, 2021, 7:37 PM
Nothing quite like starting your week off with some fun challenges! Thanks for helping wake my brain up!
Sep 27, 2021, 7:38 PM
FYI, I DRY'ed that reference filter up a little with this. Just not sure how to replace the
document.[parentArray].map
in a meaningful way. I tried to create a const and pass the projects in the filter params but that resulted in
undefined.map
errors
options: {
            filter: ({document}) => {
              const existingReferences = document.projects.map(item => item._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingReferences)',
                params: {
                  existingReferences
                }
              }
            }
          }
Sep 27, 2021, 8:27 PM
FYI, I DRY'ed that reference filter up a little with this. Just not sure how to replace the
document.[parentArray].map
in a meaningful way. I tried to create a const and pass the projects in the filter params but that resulted in
undefined.map
errors
options: {
            filter: ({document}) => {
              const existingReferences = document.projects.map(item => item._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingReferences)',
                params: {
                  existingReferences
                }
              }
            }
          }
Sep 27, 2021, 8:27 PM
I got it working with:
// ./utils/getFilter.js

export const getFilter = (document , field) => {
  const existingEntries = document[field].map(existingEntry => existingEntry._ref).filter(Boolean)
  return {
    filter: '!(_id in $existingEntries)',
    params: {
      existingEntries
    }
  }
}
Then in your doc:

import { getFilter } from "../utils/getFilter"

export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              return getFilter(document, 'projects')
              }
            }
        }
      ],
      
    }
  ]
}
Sep 27, 2021, 9:00 PM
user M
You're a superstar!! 🌟 qq would I put the
/utils/getFilter.js
in my parent
studio
directory or within
schemas
?
nvm, reading your import and util header helped me figure out the correct answer is
/studio/utils...
Sep 27, 2021, 9:35 PM
Aw, thanks! Hmm, I usually put the
utils
or
lib
folders directly underneath the root Studio folder.
Sep 27, 2021, 9:38 PM
There's a really good guide on setting up your folder structure somewhere around here but I can't find it right now. I'll link it if I do!
Sep 27, 2021, 9:40 PM

Sanity.io – build remarkable experiences at scale

Sanity is a customizable solution that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Categorized in