Comments (16)
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 suspense
. With, say, modal forms, it's quite convenient to get the form data inside the actual form.
What suspense
allowed me to do is to suspend the whole modal content until the data is present and render a, say, Skeleton
layout in place of the form. Once I get the data, the form's gonna render and get the fresh non-undefined
defaultValues
.
On the topic of undefined
data (and I know the article isn't about RHF; just some additional info): at RHF, we recommend to reset
the form's values (including defaultValues
) when the data arrives as a side-effect like
const { data } = useQuery()
const {} = useForm()
useEffect(() => {
if (data) {
reset(data))
}
}, [data), reset])
P.S. Thx for choosing RHF and your kind about the package!
from blog-comments.
@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.
from blog-comments.
@Moshyfawn that suspense
idea is awesome, thanks for pointing me in that direction. I haven't really used suspense much, and for this case, it's pretty perfect, as it simplifies the code and takes away the unwanted component split. Solid advice
at RHF, we recommend to reset the form's values (including defaultValues) when the data arrives as a side-effect
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 data
changes, even if the user has already started to modify that data. It's similar to the non-solution I have shown in putting props to useState. I know that react-query challenges / changes the way we view state, and you've also mentioned that RHF doesn't have an idiomatic way at the moment to reflect these cases. I would gladly work together with you to come up with an api that would make that possible :)
from blog-comments.
@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!!
from blog-comments.
@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.
from blog-comments.
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.
from blog-comments.
A better idea might be to just highlight values that are out of sync with the Server State and let the user decide what to do.
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”
from blog-comments.
@TkDodo
what makes you choose react-hook-form over formik
from blog-comments.
@TkDodo: I haven't really used suspense much, and for this case, it's pretty perfect
Yea, suspense
does up the game in regards to the data flow and has helped me a lot with simplifying the code along with improving the UX, for forms especially. Does require a bit of a mindset change, tho. What what in React doesn't :P
I think this is good advice with "traditional" data fetching that only happens once
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.
I would gladly work together with you to come up with an api that would make that possible :)
Thank you, Dominik! I appreciate it!
from blog-comments.
@babycourageous: Dunno how helpful I'll be but also willing to contribute any thoughts, observations, etc as a user of both these awesome libraries.
As React 18 is here, we might probably discuss including the suspense
in the scope of recommended patterns to deal with async data for default values.
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
from blog-comments.
@Noitidart I have a separate blog post about mutations: https://tkdodo.eu/blog/mastering-mutations-in-react-query
In a nutshell:
- you can track loading and error states declaratively, not only globally, but also locally. E.g. disable the submit button while the mutation is loading
- has convenient callbacks for
onSuccess
/onError
/onSettled
to give you the option to invalidate relevant queries, or perform redirects to other pages - the callbacks allow for returning promises, which will be awaited - also great for invalidation.
- the
onMutate
+ thecontext
it returns and passes to the other callbacks is very efficient for doing optimistic updates
from blog-comments.
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.
from blog-comments.
@Noitidart, RHF tracks it's own form submission state and is not bound to RQ or whatever else operation you're running inside onSubmit
. It can be 100 async calls using RQ, or a mutation and some analytics call after, that has nothing to do with RQ.
You can use RHF isSubmitting
state as an aggregated loading state of all your async operations instead of doing something like
const isSubmitting = isMutationLoading || isAnalytics || is1000MoreRequests
Think of it like RHF isSubmitting
state is the form's submission state and the mutation isLoading
state is a loading state of just this one mutation operation. It might not seem so different when you have a simple backend request inside onSubmit
, but as your app's functionality grows, you can start to see the difference.
The thing about RQ mutation here would be that you can invalidate your specific cache entry inside the mutation "life-cycle" (onMutate
, onSuccess
, onError
, onSettled
), which is super handy if you're using RQ through out your whole app.
P.S. What I said about isSubmitting
state is also applicable to all the other success
and error
states you've mentioned. I just hadn't have my coffee yet lol
from blog-comments.
@Noitidart: 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.
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
from blog-comments.
@ManuRodgers: what makes you choose react-hook-form over formik
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
from blog-comments.
I don't understand why we use useMutation
in the first examples. I still don't understand what useMutation gives us over axios.post
. Yes we can see if any mutation is in progress globally, but typically that's not used as much. Is there any benefits of useMutation here over axios.post
?
Still strugling to learn useMutaiton, great stuff though.
from blog-comments.
Related Issues (20)
- blog/testing-react-query HOT 65
- blog/react-query-and-type-script HOT 25
- blog/using-web-sockets-with-react-query HOT 5
- blog/effective-react-query-keys HOT 28
- blog/about-async-functions HOT 5
- blog/placeholder-and-initial-data-in-react-query HOT 1
- blog/react-query-as-a-state-manager HOT 24
- blog/react-query-error-handling HOT 39
- blog/use-state-vs-use-reducer HOT 5
- blog/mastering-mutations-in-react-query HOT 43
- blog/leveraging-the-query-function-context HOT 12
- blog/beware-the-leaking-any
- blog/2021-in-review HOT 7
- blog/road-to-refactoring HOT 1
- blog/always-provide-customer-value
- blog/offline-react-query HOT 1
- blog/optional-vs-undefined HOT 1
- blog/use-urgency
- blog/react-query-and-forms HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog-comments.