Validating id/schema values client side?
I have a NextJS route like
app/project/[id]
, so my id comes in from a URL param.
I want to render a 404 page if the id isn't valid.
The docs suggest casting on the client, like this:
This correctly throws if the string isn't a valid id. Should I just catch this on the client to render a 404? Or is there some way to run a validator in a client component? I was imagining something like zod.safeParse
where I could test if a string is a valid Id<"projects">
.18 Replies
There are two types of validation here:
1. Is the value the right format of string
2. Is the value pointing to a valid document
It's not currently easy to validate (1) on the client today, and (2) has to happen on the server, so I would suggest validating in the server function, and rendering the 404 based on the response. This way you could also catch the (2) case which is just as important - when you pass around IDs in strings, you probably want a way to eventually remove / delete a resource pointed to by the URL (e.g. if the URL gets leaked)
Thanks, that makes sense!
I'm still trying to figure out how to use Convex (following your docs/tutorials).
I was previously using a NextJS server action to query Postgres in a server component, so I could do what you're suggesting and 404 if validation failed (for whatever reason). I'm trying to tear that out and move to Convex.
Do you have any pointer to how I can query Convex in a server component / SSR context?
I've looked around in your docs, but they generally use the useQuery/useMutation hooks to query from client components. Any pointer to a doc/example would be great!
ConvexHttpClient is the ticket for SSR
Ok thanks, I can try going that route.
I'm using Clerk for auth and have it working client side (following your docs/example).
Any tip on how to use auth in SSR?
I'm calling
setAuth
with my Clerk token, but getting this error when I query:Here's the code that's not working.
setAuth
works, but then client.query
throws.You probably need to await setAuth
Tried that, but no luck (looks like it isn't async)
Try adding an argument to the
await getToken()
The way we call this on the client side includes template: "convex"
, see
https://github.com/get-convex/convex-js/blob/main/src/react-clerk/ConvexProviderWithClerk.tsx#L63-L66Nice, adding the template param worked!
We'll get some docs and an example project up showing SSR with Next before long, thanks for pointing this out!
That would be helpful, thanks!
You might also consider exporting
ArgumentValidationError
if you want devs to be able to handle errors like this when validation fails.@holden could you say more here, you'd want to catch and identify it?
Currently this isn't a custom error class and in general propagating errors across the server/client boundary could be error-prone but we certainly want to provide everything a developer would need here to handler errors
Sure, snippet of code below. What I'm trying to do is show a 404 page if a user tries to load
/project/[id]
with an invalid id or for a doc that doesn't exist or they can't access. Maybe there’s a better way to do this?
I think the root cause / annoying part of this experience is that validation errors throw, rather than returning something I can handle. In async data fetching libraries like useSWR or react-query, they deal with this by returning an object like {data, isLoading, error}. That makes it very easy to render something different for the loading/error states.
Throwing is especially annoying in client-side hooks, because (AFAIK) you can’t catch them without violating the rules of hooks, and dealing with an ErrorBoundary just to know if an API call succeeded isn’t great.
In this case, I don’t particularly care where I deal with the error. Could be client or server. This just feels like I'm jumping through a few too many hoops to do a simple “if validation fails, render 404”. But very possible I'm missing something about Convex!(your support has been great btw, so thanks for that!)
A helper hook that returns
const {data, isLoading, error} = useQueryObject()
is something @MapleLeaf 🍁 has asked for this too, the simple case of data | undefined
is staying but something like this is possible in user space now by wrapping useQueries
— and one will end up in convex-helpers or core before long, I agree this is often the pattern I want.
The general ArgumentValidationError
is tough because you don't have enough information to know what the problem was if there are multiple arguments. So you probably want to accept the id as a string and then validate it with db.normalizeId(idString)
and check if that's null
, then return a union. Note that in production you won't get these error messages or stack traces, it's just going to say "Server error". We have some plans for improving this, but for now you want to return a union of {data: ...}
and {error: "That project does not exist"}
.Makes sense, a simple helper or hook wrapper might go a long way here.
I hadn't understood the db.normalizeId thing, but good tip. I ended up just adding this to my query:
Doesn't throw anymore, or need a union and just treats invalid ids the same as the doc not being found. Only downside is now my id is a string instead of a typesafe id.
I ended up factoring out a util to access the convex client in SSR contexts:
This could also be a good candidate for a helper. e.g. one thing I really like about Clerk is how easy it is to access auth state in client components (with hooks) or on the server (simple async function). Would be nice if it was as easy to access my Convex queries/mutations from anywhere.
anyway, all set here, thanks for all your help!
good to hear, thanks for sharing the helper!