Using Mutations
Mutations are called directly, like a regular asynchronous function.
import updateProject from 'app/projects/mutations/updateProject'function (props) {return (<FormikonSubmit={async values => {try {const project = await updateProject(values)} catch (error) {alert('Error saving project')}}}>{/* ... */}</Formik>)}
🤔
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 use
try {} catch {}
or .catch()
.Cache Invalidation
mutate
useQuery
returns amutate
function you can use to manually update the cache- Calling
mutate
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, {mutate}] = useQuery(getProduct, {where: {id: props.query.id}})return (<FormikinitialValues={product}onSubmit={async (values) => {try {const product = await updateProduct(values)mutate(product)} catch (error) {alert("Error saving product")}}}>{/* ... */}</Formik>)}
refetch
useQuery
returns arefetch
function you can use to trigger a query reload
export default function (props) {const [product, {refetch}] = useQuery(getProduct, {where: {id: props.id}})return (<FormikinitialValues={product}onSubmit={async (values) => {try {const product = await updateProduct(values)refetch()} catch (error) {alert("Error saving product")}}}>{/* ... */}</Formik>)}
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, {mutate}] = useQuery(getProduct, {where: {id: props.query.id}})return (<FormikinitialValues={product}onSubmit={async (values) => {try {const product = await updateProduct(values)mutate(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 mutate() 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, {mutate}] = useQuery(getProduct, {where: {id: props.query.id}})return (<FormikinitialValues={product}onSubmit={async (values) => {try {// Update local cache with form values without triggering refetchmutate(values, {refetch: false})const product = await updateProduct(values)// Update local cache with result, then refetchmutate(product)} catch (error) {// Set cache back to original result, then refetchmutate(product)alert("Error saving product")}}}>{/* ... */}</Formik>)}