blog/react-query-and-forms #79
Replies: 29 comments 33 replies
-
I agree! Or another pattern is to use timestamps or include the before/after state in the mutation, the server can detect a conflict and ask the user what to do! Another pattern is to use websockets to keep state in sync across multiple clients, or display a warning “Josh is also editing this record” |
Beta Was this translation helpful? Give feedback.
-
Great great article, as always! And great insight into RQ and forms integration insight! I was splitting the form and the form data call before I started using What On the topic of const { data } = useQuery()
const {} = useForm()
useEffect(() => {
if (data) {
reset(data))
}
}, [data), reset]) P.S. Thx for choosing RHF and your kind about the package! ❤️ |
Beta Was this translation helpful? Give feedback.
-
@Moshyfawn @TkDodo love seeing maintainers of my two fave React libraries crossing paths. Just popping in to say thanks to you both for such awesome work!! |
Beta Was this translation helpful? Give feedback.
-
@Moshyfawn that
we have discussed this on twitter as well, just for posterity: I think this is good advice with "traditional" data fetching that only happens once. However, this can be dangerous with background refetches. That effect resets your whole form to the server state whenever |
Beta Was this translation helpful? Give feedback.
-
@TkDodo @Moshyfawn wow - i have been following the reset in effect pattern and not until now even considered how background refetches would impact that. Wooooops. Dunno how helpful I'll be but also willing to contribute any thoughts, observations, etc as a user of both these awesome libraries. |
Beta Was this translation helpful? Give feedback.
-
@TkDodo |
Beta Was this translation helpful? Give feedback.
-
@ManuRodgers formik uses “any” in their types so you lose some type safety when compared to react-hook-form, which is why I advocated for and switched libraries in my project personally. |
Beta Was this translation helpful? Give feedback.
-
Yea,
First off, thx for clarify this point. That's exactly the case. That's interesting that "effects like these generally [considered] an anti-pattern". RHF heavily relies on it to "reset" the form with the async "default values" that come from backend or "whatever async else". As you've correctly mentioned, "RHF doesn't have an idiomatic way at the moment to reflect ["data syncing"]. RQ is the perfect tool to look at to try improving the DX (or even the UX) in this field.
Thank you, Dominik! I appreciate it! |
Beta Was this translation helpful? Give feedback.
-
As React 18 is here, we might probably discuss including the Now, that doesn't solve the whole ever-changing backend cache data and how to deal with form values background re-fetching; that's where every input counts (pun intended lol) ;) P.S. I know, I kinda hijacked the RQ blog post on the ground of having RHF as a form management solution for the example, but the concepts and the ideas behind managing "remote" data while user interaction is universal |
Beta Was this translation helpful? Give feedback.
-
RHF stands out as a form management solution that tries to embrace the native web API as closely as possible over "just" using React, which brings out of pocket optimization measures like uncontrolled inputs and native field level validation. And it's not a biased plug as RHF maintainer lol. I've refactored a giant monolithic B2B app before I looked into working closely with RHF community :P |
Beta Was this translation helpful? Give feedback.
-
I don't understand why we use Still strugling to learn useMutaiton, great stuff though. |
Beta Was this translation helpful? Give feedback.
-
@Noitidart I have a separate blog post about mutations: https://tkdodo.eu/blog/mastering-mutations-in-react-query In a nutshell:
|
Beta Was this translation helpful? Give feedback.
-
Oh thanks that helps a lot! in the react-hook-form situation, it already has its own success/loading/error on the submit part, so was thinking useMutation here was being extra but not sure. |
Beta Was this translation helpful? Give feedback.
-
@Noitidart, RHF tracks it's own form submission state and is not bound to RQ or whatever else operation you're running inside You can use RHF const isSubmitting = isMutationLoading || isAnalytics || is1000MoreRequests Think of it like RHF The thing about RQ mutation here would be that you can invalidate your specific cache entry inside the mutation "life-cycle" ( P.S. What I said about |
Beta Was this translation helpful? Give feedback.
-
Thanks very much @Moshyfawn this is also super helpful. Haha at coffee. A note about invaliding cache entry. useMutation just allows doing that from inside its lifecycle. If I didn't useMutaiton I still could invalidate from error-handler of handleSubmit I'm thinking. |
Beta Was this translation helpful? Give feedback.
-
Sure! it's just if you want to reuse your mutation in multiple places, it better to encapsulate your data handling logic inside a custom reusable hook like function useUpdateTodo() {
const queryClient = useQueryClient()
const { mutate } = useMutation(updateTodo, {
onSuccess: () => {
queryClient.invalidateQuery(["todos"])
}
})
return {
updateTodo: mutate
}
} and call it in different places like const { updateTodo } = useCreateTodo()
// INFO: in a "Update Todo" form
const onSubmit = (todo) => {
updateTodo(todo)
}
// INFO: somewhere else, like a data grid
const onTodoCellUpdate = (todoId, todoTitle) => {
updateTodo({ todoId, todoTitle })
} It's even more prominent when you want to do an optimistic update: all your data handling logic will reside in your custom mutation hook; you just need to call the mutation method and handle the UI updates on success in your component, like closing a form dialog or highlighting an updated data grid entry // INFO: inside the useCreateTodo hook
const { mutate } = useMutation(updateTodo, {
onMutate: (todoPayload) => {
// INFO: pseudo-code _(+ might have bugs lol)_. You'll need to do a bit more to gracefully handle an optimistic update
const { todoId } = todoPayload
const currentTodos = queryClient.getQueryData(["todos"])
const updatedTodoIndex = currentTodos.findIndex(currentTodo => currentTodo === todoId)
let newTodos = [...currentTodos]
newTodos[updatedTodoIndex] = todoPayload
queryClient.setQueryData(["todos"], newTodos)
},
onSuccess: () => {
// INFO: handle request success
},
onError: () => {
// INFO: handle request error
},
onSettled: () => {
queryClient.invalidateQuery(["todos"])
}
}) As you can see, it's quite a bit of code, which is not pleasant to write every time you mutate a value. With the mutation logic encapsulated like above, you can simply do const onSubmit = (todo) => {
updateTodo(todo, {
onSuccess: () => {
closeFormDialog()
}
})
} Besides all the points above, having your data handling logic in one place (a custom mutation hook) is just a best practice and much easier to maintain. Hope that clears things for you ;) P.S. I did have coffee! xD |
Beta Was this translation helpful? Give feedback.
-
@TkDodo It's an impressive article! It helped me a lot. Can I translate the article related to React query? |
Beta Was this translation helpful? Give feedback.
-
Hey I really enjoyed this article! I am a new player in the React community and I have been playing around with RHF and React Query for the first time in order to build a personal blog. I am trying to use the default values approach to set default values to an update form that will bring in server data. I am having trouble using default values for an array of objects. I am using the useFieldArray API by RHF. I was hoping someone could point me in the right direction on how to dynamically insert the server data into each input field. |
Beta Was this translation helpful? Give feedback.
-
Great post! There's a minor typo on line 15 in the last code snippet: // rest client state back to undefined I believe you mean "reset client..." |
Beta Was this translation helpful? Give feedback.
-
In an app, using react hook form and react query, which state is suggested to use to prevent Double Submit Prevention the React Query |
Beta Was this translation helpful? Give feedback.
-
Working with forms has to be the most frustrating thing when using RQ. I always use to reuse my forms in create and edit mode, especially with the complex ones but find it almost impossible now with RQ. I constantly get undefined data, so i gave up doing this for the most part, found I wasted a lot of time. Do you or anyone else use the same forms for edit/create and also have a successful strategy you want to share? I really hate having a separate create and edit forms. |
Beta Was this translation helpful? Give feedback.
-
Hi, Thanks for the blog and actually I did meet the problem that value is But it got solved in a better way, instead of using
Like this: const facilityCameraEventsQuery = useFacilityCameraConfigurationQuery()
const methods = useForm<CreateEventFormValidationSchemaType>({
resolver: zodResolver(createEventsFormSchema),
mode: 'onChange',
defaultValues: FormDefaultValues,
// NOTE: this values will replace the default values when we fetch the data from query
// to avoid default values being undefined
values: facilityCameraEventsQuery.data,
}) You can find the explanation in the field |
Beta Was this translation helpful? Give feedback.
-
Do you know what the best practice is for handling search forms? In a nutshell, I want to take user input from a form and pass it to the I have a sandbox showing a more traditional approach https://codesandbox.io/s/ancient-rgb-3q141l, but I'm not sure how to handle it with |
Beta Was this translation helpful? Give feedback.
-
I think stale time Infinity can really get you out of sync with your server state, It need's to be used with invalidate Queries and isFetching flag to make sure that the form is getting the latest data after being updated |
Beta Was this translation helpful? Give feedback.
-
It looks like in RHF V7 you now have additional options that you can pass to Here's what that might look like now with these new APIs: function PersonDetail({ id }) {
const { data } = useQuery({
queryKey: ['person', id],
queryFn: () => fetchPerson(id),
});
const { register, handleSubmit, reset } = useForm();
const { mutate } = useMutation({
mutationFn: (values) => updatePerson(values),
});
React.useEffect(() => {
if (data) {
// This will populate our form with the data from the API, but will not clear/override the values (dirty) provided by the user
reset(data, { keepDirtyValues: true });
}
}, [data, reset]);
if (!data) {
return 'loading...';
}
return (
<form onSubmit={handleSubmit(mutate)}>
<div>
<label htmlFor="firstName">First Name</label>
<input {...register('firstName')} />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input {...register('lastName')} />
</div>
<input type="submit" />
</form>
);
} |
Beta Was this translation helpful? Give feedback.
-
It is weird. Did anyone get this TypeError: TS2345: Argument of type
UseMutateFunction<unknown, unknown, void, unknown>
is not assignable to parameter of type SubmitHandler<undefined>
Types of parameters options and event are incompatible.
Type BaseSyntheticEvent<object, any, any> has no properties in common with type
MutateOptions<unknown, unknown, void, unknown> at the line: <form onSubmit={handleSubmit(mutate)}> |
Beta Was this translation helpful? Give feedback.
-
I find myself contemplating the placement of the useMutation hook. Specifically, I'm weighing the pros and cons of defining it inside the PersonDetail component versus directly within the PersonForm. From my understanding, if the form is intended for reuse with various mutations or more specifically a reusable form, placing useMutation outside the PersonForm seems logical. However, in scenarios where the form is exclusively designed for a specific update operation, integrating useMutation directly into the PersonForm appears to be a viable approach. @TkDodo - Could you please share your insights on this? Am I correctly interpreting the best practices for implementing useMutation in such contexts? |
Beta Was this translation helpful? Give feedback.
-
About putting the form into separate file:
What if the form is in a Material UI dialog? And we're using react-hook-form. In my case "person" is passed to the dialog component from a parent, and it's not working when I set defaultValues in the dialog |
Beta Was this translation helpful? Give feedback.
-
thank you for your helping sir! but is it must to use <Controller First Name <input {...register('firstName')} /> the second question is can i implement the concept you called "Keeping background updates on" on this way First Name <input {...register('firstName')} /> i just understand this way brother |
Beta Was this translation helpful? Give feedback.
-
React Query and Forms | TkDodo's blog
Forms tend to blur the line between server and client state, so let's see how that plays together with React Query.
https://tkdodo.eu/blog/react-query-and-forms
Beta Was this translation helpful? Give feedback.
All reactions