Mutação e Revalidação
SWR fornece as APIs mutate
e useSWRMutation
para mutação de dados remotos e caches relacionados.
mutate
Existem 2 formas de usar a API mutate
para modificar os dados, a API de mutação global que consegue mudar qualquer chave e a API de mutação vinculada que só pode alterar os dados do hook SWR correspondente.
Mutação Global
A maneira recomendada de obter o modificador global é usar o hook useSWRConfig
:
import { useSWRConfig } from "swr"
function App() {
const { mutate } = useSWRConfig()
mutate(key, data, options)
}
Você também pode importá-lo globalmente:
import { mutate } from "swr"
function App() {
mutate(key, data, options)
}
Usar o modificador global apenas com o parâmetro key
não irá atualizar o cache ou disparar uma revalidação a menos que haja um hook SWR montado usando a mesma chave.
Mutação vinculada
Mutação vinculada é o caminho curto para modificar a chave atual com dados. Onde key
é vinculada a key
repassada para useSWR
e recebe data
como primeiro argumento.
É funcionalmente equivalente a função global mutate
da seção anterior, mas não requer o parâmetro key
:
import useSWR from 'swr'
function Profile () {
const { data, mutate } = useSWR('/api/user', fetcher)
return (
<div>
<h1>Meu nome é {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
// envia um request para a API para atualizar os dados
await requestUpdateUsername(newName)
// atualiza os dados locais imediatamente e revalida (refetch)
// NOTA: `key` não é necessário quando estiver usando o mutate pré-vinculado do useSWR
mutate({ ...data, name: newName })
}}>Deixe meu nome em caixa alta!</button>
</div>
)
}
Revalidação
Quando você chama mutate(key)
(ou apenas mutate()
com a API de mutação vinculada) sem nenhum dado, ele irá disparar uma revalidação (marcar os dados como expirados e disparar um refetch) para o recurso. Esse exemplo mostra como fazer refetch automático das informações de login (ex.: dentro de <Profile />
) quando o usuário clicar no botão de "Logout":
import useSWR, { useSWRConfig } from 'swr'
function App () {
const { mutate } = useSWRConfig()
return (
<div>
<Profile />
<button onClick={() => {
// define o cookie como expirado
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
// avisa todos os SWR com essa chave para revalidar
mutate('/api/user')
}}>
Logout
</button>
</div>
)
}
O hook transmite para todos os hooks SWR sob o mesmo escopo de provedor de cache. Se nenhum provedor de cache existir, irá transmitir para todos os hooks SWR.
API
Parâmetros
key
: mesmo quekey
douseSWR
, mas uma função irá se comportar como uma função filtrodata
: dados para atualizar o cache do cliente, ou uma função assíncrona para mutação remotaoptions
: aceita as seguintes opçõesoptimisticData
: dados para imediatamente atualizar o cache do cliente, ou uma função que recebe os dados atuais e retorna os novos dados do cache do cliente, normalmente usado para UI otimista.revalidate = true
: should the cache revalidate once the asynchronous update resolves. If set to a function, the function receivesdata
andkey
.populateCache = true
: o resultado da mutação remota será escrita no cache, ou uma função que recebe o novo resultado e o resultado atual como argumentos e retorna o resultado da mutação.rollbackOnError = true
: o cache deve reverter se a mutação remota der erro, ou uma função que recebe o erro lançado pelo fetcher como argumentos e retorna um boolean se deve reverter ou não.throwOnError = true
: a chamada de mutação deve lançar um erro quando falhar
Valores de Retorno
mutate
retorna os resultados em que o parâmetro data
foi resolvido. A função passada para mutate
irá retornar os dados atualizados nos quais são usados para atualizar o valor correspondente em cache. Se ocorrer um erro durante a execução da função, o erro será lançado para que possa ser lidado corretamente.
try {
const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
// Lide com o erro enqunato atualiza o usuário aqui
}
useSWRMutation
SWR também fornece useSWRMutation
como um hook para mutações remotas. As mutações remotas são apenas disparadas manualmente, ao invés de automaticamente como no useSWR
.
Também, esse hook não compartilha estado com outros hooks useSWRMutation
.
import useSWRMutation from 'swr/mutation'
// Implementação do fetcher.
// O argumento extra será passado via propriedade `arg` do segundo parâmetro.
// No exemplo abaixo, `arg` será `my_token`
async function updateUser(url, { arg }: { arg: string }) {
await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${arg}`
}
})
}
function Profile() {
// Uma API como useSWR + mutate, mas não irá começar o request automaticamente.
const { trigger } = useSWRMutation('/api/user', updateUser, options)
return <button onClick={() => {
// Dispara `updateUser` com um argumento específico
trigger('my_token')
}}>Atualizar usuário</button>
}
API
Parâmetros
key
: mesmo quekey
domutate
fetcher(key, { arg })
: uma função assíncrona para mutação remotaoptions
: um objeto opcional com as seguintes propriedades:optimisticData
: mesmo queoptimisticData
domutate
revalidate = true
: mesmo querevalidate
domutate
populateCache = false
: mesmo quepopulateCache
domutate
, mas o valor padrão éfalse
rollbackOnError = true
: mesmo querollbackOnError
domutate
throwOnError = true
: mesmo quethrowOnError
domutate
onSuccess(data, key, config)
: função callback quando a mutação remota for finalizada com sucessoonError(err, key, config)
: função callback quando a mutação remota retornar um erro
Valores de Retorno
data
: dados para a chave fornecida retornada defetcher
error
: erro lançado pelofetcher
(ou undefined)trigger(arg, options)
: uma função para disparar uma mutação remotareset
: uma função para redefinir o estado (data
,error
,isMutating
)isMutating
: se houver uma mutação remota em andamento
Uso Básico
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }: { arg: { username: string } }) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
}).then(res => res.json())
}
function App() {
const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* opções */)
return (
<button
disabled={isMutating}
onClick={async () => {
try {
const result = await trigger({ username: 'johndoe' }, /* opções */)
} catch (e) {
// lidando com erros
}
}}
>
Criar Usuário
</button>
)
}
Se você quiser usar os resultados da mutação na renderização, você pode obtê-los dos valores de retorno do useSWRMutation
.
const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)
useSWRMutation
compartilha um armazenamento de cache com useSWR
, então ele pode detectar e evitar condições de corrida entre os useSWR
. Ele também oferece suporte às funcionalidades do mutate
, como atualizações otimistas e reversão de erros. Você pode passar essas opções useSWRMutation
e sua função trigger
.
const { trigger } = useSWRMutation('/api/user', updateUser, {
optimisticData: current => ({ ...current, name: newName })
})
// or
trigger(newName, {
optimisticData: current => ({ ...current, name: newName })
})
Adiando o carregamento de dados até que seja necessário
Você também pode usar useSWRMutation
para carregar dados. useSWRMutation
nunca começa um request até que trigger
seja chamado, então você pode adiar o carregamento de dados quando realmente precisar.
import { useState } from 'react'
import useSWRMutation from 'swr/mutation'
const fetcher = url => fetch(url).then(res => res.json())
const Page = () => {
const [show, setShow] = useState(false)
// data é undefined até que trigger seja chamado
const { data: user, trigger } = useSWRMutation('/api/user', fetcher);
return (
<div>
<button onClick={() => {
trigger();
setShow(true);
}}>Mostrar Usuário</button>
{show && user ? <div>{user.name}</div> : null}
</div>
);
}
Atualizações otimistas
Em muitos casos, a aplicação de mutações locais aos dados é uma boa maneira de tornar as alterações mais rápidas, sem a necessidade de esperar pela fonte remota de dados.
Com a opção optimisticData
, você pode atualizar seus dados locais manualmente, enquanto aguarda a conclusão da mutação remota. Compondo rollbackOnError
, você também pode controlar quando reverter os dados.
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
const user = { ...data, name: newName }
const options = {
optimisticData: user,
rollbackOnError(error) {
// Se for um erro de abortamento por timeout, não reverta
return error.name !== 'AbortError'
},
}
// atualiza os dados locais imediatamente
// envia um request para atualizar os dados
// aciona uma revalidação (nova busca) para garantir que nossos dados locais estejam corretos
mutate('/api/user', updateFn(user), options);
}}>Deixe meu nome em caixa alta!</button>
</div>
)
}
O
updateFn
deve ser uma Promise ou função assíncrona para lidar com a mutação remota, deve retornar dados atualizados.
Você também pode passar uma função para optimisticData
para torná-lo dependente dos dados atuais:
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>Meu nome é {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
mutate('/api/user', updateUserName(newName), {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
});
}}>Deixe meu nome em caixa alta!</button>
</div>
)
}
Você também pode criar a mesma coisa com useSWRMutation
e trigger
:
import useSWRMutation from 'swr/mutation'
function Profile () {
const { trigger } = useSWRMutation('/api/user', updateUserName)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
trigger(newName, {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
})
}}>Deixe meu nome em caixa alta!</button>
</div>
)
}
Reversão de Erros
Quando você tem optimisticData
definido, é possível que os dados otimistas sejam exibidos para o usuário, mas a mutação remota falha. Nesse caso, você pode aproveitar rollbackOnError
para reverter o cache local ao estado anterior, para garantir que o usuário esteja vendo os dados corretos.
Atualizar Cache após Mutação
Às vezes, a solicitação de mutação remota retorna diretamente os dados atualizados, portanto, não há necessidade de fazer uma busca extra para carregá-lo. Você pode habilitar a opção populateCache
para atualizar o cache para useSWR
com a resposta da mutação:
const updateTodo = () => fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
mutate('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filtre a lista e a retorne com o item atualizado
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
revalidate: false
})
Ou com o hook useSWRMutation
:
useSWRMutation('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filtre a lista e a retorne com o item atualizado
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
// Como a API já nos fornece as informações atualizadas,
// não precisamos revalidar aqui.
revalidate: false
})
Quando combinado com optimisticData
e rollbackOnError
, você obterá uma experiência de UI otimista perfeita.
Evitando Condições de Corrida
Ambos mutate
e useSWRMutation
podem evitar condições de corrida entre useSWR
. Por exemplo,
function Profile() {
const { data } = useSWR('/api/user', getUser, { revalidateInterval: 3000 })
const { trigger } = useSWRMutation('/api/user', updateUser)
return <>
{data ? data.username : null}
<button onClick={() => trigger()}>Update User</button>
</>
}
O hook useSWR
normal pode atualizar seus dados a qualquer momento devido ao foco, polling ou outras condições. Dessa forma, o nome de usuário exibido pode ser o mais atualizado possível. No entanto, uma vez que temos uma mutação que pode ocorrer quase ao mesmo tempo de um refetch do useSWR
, pode haver uma condição de corrida em que a solicitação getUser
começa antes, mas leva mais tempo do que updateUser
.
Felizmente, useSWRMutation
lida com isso para você automaticamente. Após a mutação, ele dirá a useSWR
para descartar o request em andamento e revalidar, para que os dados obsoletos nunca sejam exibidos.
Mutação com Base nos Dados Atuais
Às vezes, você deseja atualizar uma parte de seus dados com base nos dados atuais.
Com mutate
, você pode passar uma função assíncrona que receberá o valor atual em cache, se houver, e retornará um documento atualizado.
mutate('/api/todos', async todos => {
// vamos atualizar o todo com ID `1` para ser concluído,
// esta API retorna os dados atualizados
const updatedTodo = await fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
// filtra a lista e retorna com os dados atualizados
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
// Como a API já nos fornece as informações atualizadas,
// não precisamos revalidar aqui.
}, { revalidate: false })
Modificando Múltiplos Items
A API global mutate
aceita uma função de filtro, que aceita key
como argumento e retorna quais chaves revalidar. A função de filtro é aplicada a todas as chaves de cache existentes:
import { mutate } from 'swr'
// Ou do hook se você personalizou o provedor de cache:
// { mutar } = useSWRConfig()
mutate(
key => typeof key === 'string' && key.startsWith('/api/item?id='),
undefined,
{ revalidate: true }
)
Isso também funciona com qualquer tipo de chave, como uma lista. A mutação corresponde a todas as chaves, das quais o primeiro elemento é 'item'
.
useSWR(['item', 123], ...)
useSWR(['item', 124], ...)
useSWR(['item', 125], ...)
mutate(
key => Array.isArray(key) && key[0] === 'item',
undefined,
{ revalidate: false }
)
A função de filtro é aplicada a todas as chaves de cache existentes, portanto, você não deve assumir o formato de chaves ao usar vários formatos de chaves.
// ✅ chave de lista correspondente
mutate((key) => key[0].startsWith('/api'), data)
// ✅ chave de string correspondente
mutate((key) => typeof key === 'string' && key.startsWith('/api'), data)
// ❌ ERRO: modificar chaves incertas (array ou string)
mutate((key: any) => /\/api/.test(key.toString()))
Você pode usar a função de filtro para limpar todos os dados do cache, o que é útil ao fazer logout:
const clearCache = () => mutate(
() => true,
undefined,
{ revalidate: false }
)
// ...limpar cache ao sair
clearCache()