Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

select$ operator for optional queries #183

Open
heggemsnes opened this issue May 8, 2023 · 7 comments
Open

select$ operator for optional queries #183

heggemsnes opened this issue May 8, 2023 · 7 comments

Comments

@heggemsnes
Copy link
Contributor

heggemsnes commented May 8, 2023

Hi!

As the title suggest, I'm loving both grab$ and select, but the .optional() method doesn't work the way I expect it to in regards to validation when using it like this:

q("").select(
  "_type == 'nice'": {
    title: q.string().optional()
  }
)

When using nullToUndefined it works as expected :)

@littlemilkstudio
Copy link
Contributor

@heggemsnes heres a bit of further context around this discussion

for your example, the select method conditions accept queries as well as the projection shorthand, so does using the entity query equivalent of what you have work?

q.select(
  "_type == 'nice'": q('').grab$({
    title: q.string().optional()
  })
)

@littlemilkstudio
Copy link
Contributor

just a heads up, using the entity level select q(<entity>).select() vs the property level select q.select() will result in slightly different underlying queries, with relatively similar behavior. just wanted to mention in case you're using q('').select() anywhere, it might be a little cleaner to just use q.select()

const entityQuerySelect = q('').select({
  "_type == 'nice'": q('').grab$({
    title: q.string().optional()
  })
})
console.log(entityQuerySelect.query)
/* output GROQ:
{ 
  ...select(
    _type == 'nice' => { title }
  ) 
}
*/

const propertyLevelSelect = q.select({
  "_type == 'nice'": q('').grab$({
    title: q.string().optional()
  })
})
console.log(propertyLevelSelect.query)
/* output GROQ:
select(
  _type == 'nice' => { title }
) 
*/

@heggemsnes
Copy link
Contributor Author

Hei @littlemilkstudio thanks for your input here.

I'm mostly doing the q.select() see this example:

export const linksSelect = q.select({
  "_type == 'externalLinkObject'": {
    _key: q.string(),
    _type: q.literal("externalLinkObject"),
    ...externalLinkQuery,
  },
  "_type == 'internalLinkObject'": {
    _key: q.string(),
    _type: q.literal("internalLinkObject"),
    ...internalLinkQuery,
  },
  "_type == 'downloadLinkObject'": {
    _key: q.string(),
    _type: q.literal("downloadLinkObject"),
    ...downloadLinkQuery,
  },
  default: {
    linkType: q.literal("unknown"),
    link: q("link").grab({
      title: q.string().nullable(),
    }),
    _key: q.string(),
  },
})

While I realise that this does what I want:

 "_type == 'downloadLinkObject'": q("").grab$({
    _key: q.string(),
    _type: q.literal("downloadLinkObject"),
    ...downloadLinkQuery,
  }),

It just feels like a bit unnecessary boilerplate. I think a discussion around nullable/optionals and what should be the default would be nice since .nullable() breaks when using .grab$() in my experience.

@littlemilkstudio
Copy link
Contributor

I think a discussion around nullable/optionals and what should be the default would be nice

Yea absolutely. This can be kind of confusing.

So .nullable() will break with .grab$() because .grab$() uses a preprocess to convert null -> undefined. Since this process is run before any validation, expecting a field to be null will cause an error because the preprocess will have already converted null to undefined.

As far as which you should default to using between .optional() .nullable() or .nullish(), I think it depends on the desired behavior.

Interacting with sanity and groq directly, if a field is queried and a value does not exist, it will return null, so I would default to using .nullable() with .grab().

This becomes interesting if you want to supply a default value for a field on validation if it doesn't exist. In such a scenario, q.string().nullable().default('bar') will not work because zod will only supply the default if the value of the field is undefined. In such a case, it's better to reach for .grab$() so you can validate the field with q.string().optional().default('bar') and have the field default to 'bar' if it doesn't exist in sanity.

Lastly, if you are unsure, or want to pass the same selection between to both .grab() & .grab$() calls, you can use .nullish() which is the equivalent of .optional().nullable() and that just says the field can either be undefined or null and both are valid in your schema.

@littlemilkstudio
Copy link
Contributor

While I realize that this does what I want It just feels like a bit unnecessary boilerplate

Yea totally hear you. Another 2 options you can use here are:

  1. nullToUndefined helper which will pretty much do the same thing as q('').grab$():
"_type == 'downloadLinkObject'": nullToUndefined({
    _key: q.string(),
    _type: q.literal("downloadLinkObject"),
    ...downloadLinkQuery,
  }),
  1. Just make properties in downloadLinkQuery be .nullable() or .nullish() vs .optional() if you're not utilizing .default() for them

I think something like a .select$() isn't as straight forward as a .grab$() because.select() is a lot more flexible than .grab(). .select() kinda just acts like a switch statement with minimal opinions about your schema for each case. Anyway, I'm not completely opposed to a .select$(), but just still thinking through it a bit.

Does the clarification on .nullable() vs .nullish() vs .optional() clear up the need for .select$() at all in your scenario? or do you think you's still find a .select$() as useful here?

@heggemsnes
Copy link
Contributor Author

Hey @littlemilkstudio thanks for taking the time here! I was not aware I could do nullToUndefined on a object level. Looks interesting!

I was also not aware about nullish(). Sounds perfect our use case :)

@littlemilkstudio
Copy link
Contributor

Awesome! If you're still running into scenarios where you think a .select$() feature might be more useful, please post back here. Would love to hear about them since I'd still consider .select$() under active consideration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants