DEV Community

Sergio Daniel Xalambrí
Sergio Daniel Xalambrí

Posted on • Originally published at sergiodxa.com

2 1

Using Immer with SWR to mutate data

SWR comes with a nice function called mutate that let you change the cached data for a given key, it comes with multiple different options but one of the nicest one is to pass a function, get the current data and update it, from anywhere.

import { mutate } from "swr"

mutate("/api/me", user => ({ ...user, name: "Sergio" }))
Enter fullscreen mode Exit fullscreen mode

This function update the name of the user cached as /api/me, and if there is a component subscribed to that cache key it will trigger a revalidation, this is however something we can disable passing a false as third argument.

If you see the example above we used restructuring to copy the user object, this is required because SWR uses object reference to avoid unnecessary re-renders.

Enter Immer, this small lib let you mutate an object while performing an immutable change, let's see the example updated

import { mutate } from "swr"
import produce from "immer'

mutate("/api/me", produce(user => {
  user.name = "Sergio"
}))
Enter fullscreen mode Exit fullscreen mode

This produce function will receive a callback and it will execute it passing a "draft" copy of the object, in this draft we could mutate the object however we want, then produce will return a new function that receives the original object to mutate, it run our callback, and generate a new object with our changes applied, lastly it will return the new updated object.

Looks like a lot of work, and it is, however for the user of Immer it's only a few lines as we saw above, what will happen combined with mutate is that the returned function will be passed to mutate as we did before and it will receive the cached value, then it will return the updated value and it will update the cache, triggering a re-render of any component subscribed to that cache key.

And this is great because it allow use to implement Optimistic UI easier, let's see an example

import React from 'react'
import useSWR, { mutate } from 'swr'
import produce from "immer"

async function fetcher(...args) {
  const res = await fetch(...args)
  return await res.json()
}
export default () => {
  const [text, setText] = React.useState('');
  const { data } = useSWR('/api/data', fetcher)

  async function handleSubmit(event) {
    event.preventDefault()

    // in this mutate call we will optimistically update the UI
    mutate("/api/data", produce(draftData => {
      draftData.push(text) // here we push the new text
    }), false) // we don't trigger a revalidation

    // in this mutate call, we will POST the API and update the UI
    // if the update fails it will rollback the optimistic change
    await mutate('/api/data', await fetch('/api/data', {
      method: 'POST',
      body: JSON.stringify({ text })
    }))

    setText('') // after the update we clear the input
  }

  return <div>
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        onChange={event => setText(event.target.value)}
        value={text}
      />
      <button>Create</button>
    </form>
    <ul>
      {data ? data.map(datum => <li key={datum}>{datum}</li>) : 'loading...'}
    </ul>
  </div>
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay