🚀Announcing Flightcontrol - Easily Deploy Blitz.js and Next.js to AWS 🚀
Back to Documentation Menu

Using Mutations

Topics

Jump to a Topic

In a React Component

To use one of your Blitz mutations, call the useMutation hook with your mutation resolver.

It returns this tuple array,[invoke, mutationExtras], where invoke is an asynchronous function that you can invoke directly inside an event handler or effect.

useMutation is a thin layer on top of the useMutation hook from react-query.

import {useMutation} from '@blitzjs/rpc'
import updateProject from 'app/projects/mutations/updateProject'

function (props) {
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      onSubmit={async values => {
        try {
          const project = await updateProjectMutation(values)
        } catch (error) {
          alert('Error saving project')
        }
      }}>
      {/* ... */}
    </Formik>
  )
}

For complete details, see the useMutation documentation.

🤔

You may be wondering how that can work since it's importing server code into your component: At build time, the direct function import is swapped out with a network call. So the query function code is never included in your client code.

Error Handling

Any errors thrown from your server code will be thrown on the client exactly like you'd expect. So you can wrap the invoke in try {} catch {} or .catch().

Cache Invalidation

setQueryData()

  • useQuery returns a setQueryData function you can use to manually update the cache
  • Calling setQueryData will automatically trigger a refetch for the initial query to ensure it has the correct data. Disable refetch by passing an options object {refetch: false} as the second argument.
export default function (props: { query: { id: number } }) {
  const [product, { setQueryData }] = useQuery(getProduct, {
    where: { id: props.query.id },
  })
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      initialValues={product}
      onSubmit={async (values) => {
        try {
          const product = await updateProjectMutation(values)
          setQueryData(product)
        } catch (error) {
          alert("Error saving product")
        }
      }}
    >
      {/* ... */}
    </Formik>
  )
}

refetch()

useQuery returns a refetch function you can use to trigger a query reload.

export default function (props) {
  const [product, { refetch }] = useQuery(getProduct, {
    where: { id: props.id },
  })
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      initialValues={product}
      onSubmit={async (values) => {
        try {
          const product = await updateProjectMutation(values)
          refetch()
        } catch (error) {
          alert("Error saving product")
        }
      }}
    >
      {/* ... */}
    </Formik>
  )
}

keywords: refresh, revalidate, invalidate

invalidateQuery()

Another way, if you need to invalidate a query inside another component, is to use the invalidateQuery() function provided by blitz.

Example
import { invalidateQuery } from "@blitzjs/rpc"
import getProducts from "app/products/queries/getProducts" const Page = function ({ products }) { return ( <div> <button onClick={() => {
invalidateQuery(getProducts)
}}
>
Invalidate </button> </div> ) } export default Page

Automatically

We want to somehow automatically invalidate your query cache. We're not sure how to do this yet, but we have an open issue that you're welcome to contribute to!

Optimistic Updates

Optimistic UI patterns are a technique for making your apps feel more responsive to user interactions. When a user takes an action (for example liking a post) we can avoid showing activity spinners or loading animations by simply assuming the action was successful. We update our local cache and interface immediately to show a successful result.

When the real response arrives from the action we then update the UI to reflect the true value returned from the database. In most cases, with a successful request, this will be invisible to the user, and the change will appear to have occurred instantly.

In the case of an error, we revert the local change, refetch the data, and optionally show an error message.

Here's a simple form component with an onSubmit function that calls an updateProduct mutation to save edits.

export default function (props: { query: { id: number } }) {
  const [product, { setQueryData }] = useQuery(getProduct, {
    where: { id: props.query.id },
  })
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      initialValues={product}
      onSubmit={async (values) => {
        try {
          const product = await updateProjectMutation(values)
          setQueryData(product)
        } catch (error) {
          alert("Error saving product")
        }
      }}
    >
      {/* ... */}
    </Formik>
  )
}

We can enhance this to support instant feedback to the user, immediately showing the edited product information, by mutating the local cache of the getProducts query with the changes we're about to send to the database. We pass {refetch: false} as a second argument to the setQueryData() function to prevent the mutation triggering a refetch of stale data while we wait for the response.

export default function (props: { query: { id: number } }) {
  const [product, { setQueryData }] = useQuery(getProduct, {
    where: { id: props.query.id },
  })
  const [updateProjectMutation] = useMutation(updateProject)

  return (
    <Formik
      initialValues={product}
      onSubmit={async (values) => {
        try {
          // Update local cache with form values without triggering refetch
setQueryData(values, { refetch: false })
const product = await updateProjectMutation(values) // Update local cache with result, then refetch setQueryData(product) } catch (error) { // Set cache back to original result, then refetch
setQueryData(product)
alert("Error saving product") } }} > {/* ... */} </Formik> ) }

If the data is not available in the same component where the optimistic update occurs, then getQueryData can be used to get the cached data.

export default function (props: { query: { id: number } }) {
  const [updateProjectMutation] = useMutation(updateProject, {
    onMutate: async (project) => {
      // Get products from the cache
const products = getQueryData(getProjects, {})
// If products is not in the cache, then return if (!products) return // Update local cache with form values without triggering refetch await setQueryData([...products, project], { refetch: false }) // Return a context with the previous projects return { projects } }, // If the mutation fails, use the context returned from onMutate to roll back onError: async (err, project, context) => { await setQueryData(getProjects, {}, context.projects, { refetch: false, }) }, // Always refetch after error or success: onSettled: () => { await invalidateQuery(getProjects, {}) }, }) return ( <Formik initialValues={product} onSubmit={async (values) => { try { await updateProjectMutation(values) } catch (error) { alert("Error saving product") } }} > {/* ... */} </Formik> ) }

Idea for improving this page? Edit it on GitHub.