Giter VIP home page Giter VIP logo

Comments (16)

Moshyfawn avatar Moshyfawn commented on August 17, 2024 6

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.

joshribakoff avatar joshribakoff commented on August 17, 2024 4

@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.

TkDodo avatar TkDodo commented on August 17, 2024 3

@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.

babycourageous avatar babycourageous commented on August 17, 2024 2

@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.

babycourageous avatar babycourageous commented on August 17, 2024 2

@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.

Noitidart avatar Noitidart commented on August 17, 2024 2

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.

joshribakoff avatar joshribakoff commented on August 17, 2024 1

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.

ManuRodgers avatar ManuRodgers commented on August 17, 2024 1

@TkDodo
what makes you choose react-hook-form over formik

from blog-comments.

Moshyfawn avatar Moshyfawn commented on August 17, 2024 1

@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.

Moshyfawn avatar Moshyfawn commented on August 17, 2024 1

@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.

TkDodo avatar TkDodo commented on August 17, 2024 1

@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 + the context it returns and passes to the other callbacks is very efficient for doing optimistic updates

from blog-comments.

Noitidart avatar Noitidart commented on August 17, 2024 1

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.

Moshyfawn avatar Moshyfawn commented on August 17, 2024 1

@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.

Moshyfawn avatar Moshyfawn commented on August 17, 2024 1

@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.

Moshyfawn avatar Moshyfawn commented on August 17, 2024

@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.

Noitidart avatar Noitidart commented on August 17, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.