I'm building a form (based on react-hook-form) to filter a list of events by start and end date. The list is retrieved with react-query's useQuery()
call. There's a zod-based validation in place (via @hookform/resolvers) to make sure the end date always comes after the start date.
I tried various approaches but none of them really worked the way I expected (my examples use tRPC, but this shouldn't change things):
onSubmit()
handler.const [query, setQuery] = useState({ startDate: initialStartDate, endDate: initialEndDate });
const { handleSubmit } = useForm({
mode: 'onChange',
schema: myValidationSchema,
});
const events = trpc.bookings.events.useQuery(query, {
keepPreviousData: true
});
return (
<form onSubmit={handleSubmit((values) => { setQuery(values) })}>
...
</form>
)
This almost works, but it's somewhat weird that the query is automatically run when the component mounts. Also, what if there is no initial default state which satisfies the query? Imagine I don't want to default to a startDate
and endDate
but leave this for the user to fill out before running the query? {}
does not satisfy the query constraints because startDate
and endDate
are required.
I couldn't get this to work with the form validation. Queries were run although the form was in an invalid state.
const { watch } = useForm({
mode: 'onChange',
schema: myValidationSchema,
});
const query = watch();
const events = trpc.bookings.events.useQuery(query, {
keepPreviousData: true,
});
I tried adding enabled: formState.isValid
to the useQuery()
options, but isValid
does not represent the state of the validation accurately (probably due to its reactive nature). When logging it to the console, I can see it flapping between true
and false
in short succession when changing fields. The truthy state triggers the query with invalid values, which is what I'm trying to prevent in the first place.
enabled: false
and refetch()
in the onSubmit()
handler.This has the same drawback as the first two approaches: The query needs an initial state (which I might not have). Also, changing options in the form automatically updates the list of results when they have been fetched beforehand (because they're served from react-query's cache).
useMutation
instead of useQuery
This would probably work best because it's easy to programmatically trigger the request with validated data from the form in the onSubmit()
handler, but I haven't even tried it, because it just feels so wrong to use a POST
request when a GET
request is clearly the way to go here.
I feel like I must be doing something terribly wrong here, because the task is so common (filter a list of things by valid values from a form) and react-query is giving me such a hard time.
Interesting question, here are my thoughts for the approaches:
You definitely need enabled
in some way if you don't have initialData and / or do not want to run the query immediately.
The fact that trpc does require you to provide an object with valid { startDate, endDate }
is likely something you need to change. Make them optional or allow passing no parameter at all, then reject on the server if you don't have variables yet. This is similar to what I'm outlining here in my blog.
Don't do enabled:false
+ refetch or useMutation
. It's not the right tool for this.
Watching with enabled: formState.isValid
is neat but it really requires isValid
to be "stable" across re-renders. What you are reporting sounds like a potential issue in react-hook-form
.