React Hook SWR - Melhor UX no consumo de API no Front End React
✅ Pré-requisitos
No decorrer do post, vamos mostrar alguns exemplos práticos. Para melhor experiência na leitura do post, você precisa entender:
- JavaScript (ES06 à ES08), Promises, React, React Hooks, fetch API, Axios e básico de TypeScript.
🔰 Introdução
Vercel criou o React Hook SWR para entregar uma melhor experiência ao usuário (UX), o segredo está em como a ferramenta lida com os dados que foram buscados independente da implementação do back end (Java, Node.js PHP) e da API que a realiza a busca no Front End, por exemplo fetch API do JavaScript e Axios amplamente utilizados para essa finalidade.
A ferramenta em questão não substitui o fetch API ou Axios, ela é um camada que executa em cima dessas tecnologias — como um Wrapper (algo que envolve). Abaixo veremos com mais detalhes.
SWR é uma sigla para Stale-While-Revalidate (tradução literal: traga os dados antigos enquanto avalia se tem novos dados). Essa é uma estratégia que pega os dados do cache e depois, por baixo dos panos, é feito uma requisição ao back end para trazer os novos dados se possuir, recarregando a listagem com os novos dados, de maneira que fique transparente para o usuário.
Com o SWR, os componentes vão receber o fluxo de atualizações de dados constante e automaticamente. A interface do usuário será sempre rápida e reativa.
O que o SWR entrega:
- Um rápido, leve e reusável data fetching;
- Cache integrado e evita redundância de requisição;
- Experiência Real-time;
- Suporte ao TypeScript;
- Possibilidade de ser utilizado no React Native;
- Navegação rápida entre páginas;
- Revalidação dos dados quando a tela recebe foco (revalidate-on-focus);
- Revalidação de dados quando reconecta à Internet;
- Mutação de dados locais com (Optimistic UI);
- React Suspense;
- API agnóstica, pode ser utilizado com REST ou GraphQL;
- Back end agnóstico, não importa em qual linguagem foi implementado.
🤝 Conhecendo a API do SWR:
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
data
= os dados retornados da API;error
= caso algum erro tenha ocorrido;isValidating
= Boolean que retornatrue
se estiver buscando os dados da API efalse
quando já retornou os dados, ou seja, já foi feita a validação;mutate
= função que irá alterar algum valor no cache.
👊 SWR na Prática
Para começar a usar o SWR é necessário instalar a lib na aplicação React:
yarn add swr
Depois é necessário criar um hook que recebe uma string com a URL e retorna os dados e um erro:
import useSWR from "swr";
export function useFetch(url: string) {
const { data, error } = useSWR(url, async (url) => {
const response = await fetch(url);
const data = await response.json();
return data;
});
return { data, error };
}
useFetch
é um hook customizado que estamos criando. Dentro dela tem a chamada para um um outro hook customizado useSWR
que importamos da biblioteca swr.
O hook recebe uma string e uma função assíncrona, perceba que ela é um wrapper pois envolve a chamada à API logo abaixo, que nesse caso estamos usando fetch API que recebe URL do servidor e retorna os dados assim que a promise for resolvida, e por fim retorna o dado e o erro que são retornadas do hook useFetch.
Só isso e já podemos ver todo o poder do SWR no front end.
Para demonstrar o suporte ao TypeScript e a possibilidade de usar axios, podemos criar o mesmo código de cima, dessa maneira:
import useSWR from 'swr'
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3333'
})
export function useFetch<Data = any, Error = any>(url: string) {
const { data, error } = useSWR<Data, Error>(url, async url => {
const response = await api.get(url);
return response.data;
})
return { data, error }
}
Usando o useSWR temos como vantagem deixar o componente mais enxuto, evitando utilizar o useEffect
para requisição, observe as diferenças abaixo:
- Sem o SWR:
const UserList: React.FC = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("users").then((response) => {
response.json().then((users) => {
setData(users);
});
});
}, []);
return (
<ul>
{data.map((user) => (
<li key={user.id}>
<Link to={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
);
};
export default UserList;
- Agora com SWR:
const UserList: React.FC = () => {
const { data } = useFetch("users");
if (!data) {
return <p>Carregando...</p>;
}
return (
<ul>
{data.map((user) => (
<li key={user.id}>
<Link to={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
);
};
export default UserList;
Observe que ficou mais simples.
Enquanto os dados não chegam, fica exibindo uma mensagem de Carregando.... na tela. Essa é uma das vantagens de usar o SWR, código fica mais limpo.
O SWR sempre busca os dados no cache e depois é feito a revalidação, buscando os dados do servidor.
🌀 Mutação de Dados
Utilizando a estratégia de Optimist UI (Interface otimista), é exibido os dados na tela do usuário como se já estivesse dado certo alguma alteração, inclusão ou deleção. Por baixo dos panos, o back end faz o seu trabalho, porém na interface já exibe como se o dado já estivesse pronto, isto é, feito toda operação no back end.
Essa é uma técnica bem interessante para dar uma sensação de velocidade para o usuário, e tem um caso de uso especifico, sempre que um ação "dá certo" no back end pode ter essa técnica implementada no front end.
👩💻 Exemplo prático da utilização do mutate
Precisamos que o hook useSWR retorne o mutate
e que o mesmo seja retornado no hook useFetch
:
import useSWR from 'swr'
import api from '../services/api';
export function useFetch<Data = any, Error = any>(url: string) {
const { data, error, mutate } = useSWR<Data, Error>(url, async url => {
const response = await api.get(url);
return response.data;
})
return { data, error, mutate }
}
Agora podemos utilizar essa função nos componentes:
- Código:
import React, { useCallback } from 'react';
import { Link } from 'react-router-dom';
import { mutate as mutateGlobal } from 'swr';
import { useFetch } from '../hooks/useFetch';
import api from '../services/api';
interface User {
id: number;
name: string;
}
const UserList: React.FC = () => {
const { data, mutate } = useFetch<User[]>('users');
const handleNameChange = useCallback((id: number) => {
api.put(`users/${id}`, { name: 'Bartolomeu' });
const updatedUsers = data?.map(user => {
if (user.id === id) {
return { ...user, name: 'Bartolomeu' }
}
return user;
})
mutate(updatedUsers, false)
mutateGlobal(`users/${id}`, { id, name: 'Bartolomeu' })
}, [data, mutate]);
if (!data) {
return <p>Carregando...</p>
}
return (
<ul>
{data.map(user => (
<li key={user.id}>
<Link to={`/users/${user.id}`}>
{user.name}
</Link>
<button type="button" onClick={() => handleNameChange(user.id)}>
Alterar nome
</button>
</li>
))}
</ul>
);
}
export default UserList;
Tem um botão que quando clicado chama a função handleNameChange
a qual chama a api na rota users/${id}
informando o name
como parâmetro da requisição.
Todas as chamada à uma API são assíncronas, mas nesse caso não estamos usando await
para esperar que requisição seja concluída, com isso o código continua a sua execução, sem aguardar o término da instrução.
É criado um novo array com os dados alterados e repassado para a função mutate
que irá atualizar o cache e renderizar essa alteração, independente do que for acontecer no back end.
A flag false
, no segundo parâmetro da função mutate
, indica que não deve fazer a busca dos dados, uma vez que eles ainda podem não ter sido salvos no banco de dados. Vale lembrar que não estamos utilizando o await
na alteração.
A função renomeada para mutateGlobal
atualiza os dados em outras rotas, o mutate
que usamos altera apenas no componente local.
Uma vez que alteramos de maneira otimista a listagem de usuários (UserList), precisamos alterar também o detalhe do usuário (UserDetail), caso contrário a listagem fica atualizada, mas os detalhes do usuário não, gerando inconsistência.
Então, observamos que alguns cuidados devem ser tomados na implementação dessa técnica.
Com SWR a requisição não fica mais rápida, mas a maneira de como é gerenciado o cache da aplicação melhora tanto a experiência do usuário que deixa essa impressão.
🧑💻 Experimentando o SWR
Se quiser experimentar a aplicação, inicie o servidor abrindo essa página:
E depois abra a aplicação front end:
Pode fazer o fork desse código para sua conta no CodeSandbox e testar à vontade.
👍 Conclusão
SWR melhora a experiência do usuário que utiliza a aplicação, criando a sensação do app estar mais rápido. Para o dev, no entanto, ele tem à sua disposição funções que simplificam a implementação de estratégias de revalidação de dados, cache, melhorias na busca, entre outros benefícios que podemos encontrar na documentação oficial. Ou seja, SWR amplia a experiência em dois caminhos: desenvolvedores e usuários.
SWR não está sozinho no mercado, tem o React Query que segue a mesma proposta.
Aqui na Rocketseat estamos utilizando o SWR. Melhora bastante a usabilidade da aplicação.
O Diego Fernandes gravou um vídeo explicando direto ao ponto as principais funcionalidades do SWR. Vale a pena dar uma conferida:
🔗 Links
- https://github.com/vercel/swr
- https://swr.vercel.app/
- https://github.com/rocketseat-content/youtube-react-swr
E aí, o que achou do post?
Espero que tenha curtido! 💜
O aprendizado é contínuo e sempre haverá um próximo nível! 🚀