Options collection has tags.
Inside block I want to select only options that have particular tag.
What is correct approach here?
Would be great if filter can be applied without saving document first.
Currently I have this. I tried to use filterOptions, but got confused by syntax and still got all items regardless
// options collection
{
name: 'tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true,
},
// tags collection
{
name: 'name',
type: 'text',
required: true,
},
// block that needs to select options with chosen tags
{
name: 'tags',
type: 'relationship',
relationTo: 'tags', // options document needs to have this tag
hasMany: true,
},
{
name: 'show',
type: 'relationship',
relationTo: 'options', // to appear in this list
hasMany: true,
},Hi happy to try to help. If I understand your challenge correctly, you want a collection (that's not options or tags) to be able to select tags and as a result have option selections filtered by those tags?
If so, try something like this (note I haven't tested this so it's bound to need some debugging!). Hope it helps.
Other Collection:
{
name: 'tags',
label: 'Tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true,
},
{
name: 'options',
label: 'Options',
type: 'relationship',
relationTo: 'options',
filterOptions: async ({ relationTo, data }): Promise<Where> => {
if(relationTo === 'options') {
if (data?.tags?.relationTo === 'tags' && data?.tags?.value) {
try {
// You'll need to get all options for the subset of tags
// not sure if there's a better way but this should work
const payload = await getPayload({ config: configPromise })
const result = await payload.find({
pagination: false,
collection: 'options' as CollectionSlug,
where: {
'tags.value': { equals: data.tags.value },
},
depth: 0,
})
// Return an array of IDs from the result documents
const optionValues = result?.docs.map(doc => doc.id) ?? []
return {
options: {
equals: {
relationTo: 'options',
value: { id: { in: optionValues } },
},
},
}
} catch (error) {
console.error('Error getting options:', error)
return {}
}
}
}
return {}
},
hasMany: true,
admin: {
allowCreate: false,
allowEdit: false,
}
},Like this I get all options, with or without tag
RE: Getting all options, you may need to add a test that checks if no tags are selected and return nothing:
if (!data?.tags?.value) {
// Return a where clause that will result in no matches
return {
id: {
in: []
}
}
}
Actually a good idea to add that as default and as the result of the error in try/catch as well. Double checking my logic for the filter itself now.
Apologies, as mentioned, I can't test this at the moment - try this - adjusted to simplify, query the correct collection (i.e. tags 🤦♀️ ) and also refactored for tags likely being an array due to hasMany. Hopefully this is closer! Can you share any console errors if it doesn't work?
filterOptions: async ({ relationTo, data }): Promise<Where> => {
if(relationTo === 'options') {
// Check if tags field exists and has values
if (data?.tags && Array.isArray(data.tags) && data.tags.length > 0) {
try {
// First get the selected tags' associated options
const selectedTags = await payload.find({
collection: 'tags',
where: {
id: {
in: data.tags.map(tag => tag.value)
}
},
depth: 0
});
// Extract all option IDs from the selected tags
const optionIds = selectedTags.docs.reduce((acc, tag) => {
if (tag.options && Array.isArray(tag.options)) {
const tagOptionIds = tag.options.map(opt => opt.value);
return [...acc, ...tagOptionIds];
}
return acc;
}, []);
return {
id: {
in: optionIds
}
}
} catch (error) {
console.error('Error getting options:', error)
return {
id: { in: [] }
}
}
}
return {
id: { in: [] }
}
}
return {
id: { in: [] }
}
}I am 99% confident it will not work because options have tags, not tags have options
No errors, it just shows full lst
I did this and looks like it works. Is it right though?
filterOptions: async ({
relationTo,
siblingData,
}): Promise<Where> => {
if (relationTo !== 'options') {
return {
id: { exists: true },
}
}
const tagIds = (siblingData as ShowListBlockType).tags?.map((tag) => {
if (typeof tag === 'string') {
return tag
}
return tag.id
})
const payload = await getPayload({
config: payloadConfig,
})
const allOptions = (
await payload.find({
collection: 'options',
})
).docs
const filteredOptions = allOptions.filter((option) => {
const optionTagIds = option.tags
?.map((tag) => {
if (typeof tag === 'string') {
return null
}
return tag.id
})
?.filter((option) => option !== null)
if (!optionTagIds?.length) {
return false
}
return optionTagIds.every((optionTagId) =>
tagIds?.includes(optionTagId),
)
})
const optionIds = filteredOptions.map((option) => option.id)
return {
id: {
in: optionIds,
},
}
},id: { in: [] }is bugged, it returns all instead of none
Star
Discord
online
Get dedicated engineering support directly from the Payload team.