Nesse modelo, as atualizações são transmitidas por websockets (socket.io) e focadas nas abas/janelas dos usuários da mesma empresa.
Last active
February 6, 2022 21:58
-
-
Save geovanisouza92/d52cd8a4e2d1c9f0b535f73448ea69b5 to your computer and use it in GitHub Desktop.
Realtime com react-query based on https://tkdodo.eu/blog/using-web-sockets-with-react-query
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// environments/environment.js | |
export default { | |
realtimeEndpoint: 'ws://localhost:3000/' | |
} | |
// environments/environment.prod.js | |
export default { | |
realtimeEndpoint: 'wss://app-domain.com/' | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// lib/notes.js | |
// Nota: essas funções podem ser reutilizadas para prefetch | |
function fetchNotes() { | |
return await fetch('/api/notes').then(res => res.json()); | |
} | |
function fetchNote(id) { | |
return await fetch(`/api/notes/${id}`).then(res => res.json()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// lib/realtime.js | |
import { useQueryClient } from 'react-query'; | |
import { io } from 'socket.io-client'; | |
import environment from './environments/environment'; | |
function useRealtime() { | |
const queryClient = useQueryClient(); | |
useEffect(() => { | |
const socket = io(environment.realtimeEndpoint); | |
socket.on('invalidate', (key) => { | |
queryClient.invalidateQueries(key); | |
// key == ['notes', 'list'] invalida lista de notas | |
// key == ['notes', 'detail', 5] invalida nota #5 | |
// key == ['notes'] invalida tudo relacionado a notas | |
}); | |
// Atualização parcial de dados, assumindo que cada item tem um ID. | |
// Se o item não existir, ele é criado no cache. | |
socket.on('replace', (key, data) => { | |
queryClient.setQueryData(key, (oldData) => { | |
const update = (entity) => entity.id === data.id ? { ...entity, ...data } : entity; | |
return Array.isArray(oldData) ? oldData.map(update) : update(oldData); | |
}); | |
}); | |
return () => socket.disconnect(); | |
}, []); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// pages/notes/[id].jsx | |
import { useRouter } from 'next/router'; | |
import { useQuery } from 'react-query'; | |
import { fetchNote } from './lib/notes'; | |
export default function NotePage() { | |
const { query } = useRouter(); | |
// Vai re-renderizar se a nota for invalidada | |
const { data } = useNote(query.id); | |
useRealtime(); | |
// ... | |
} | |
function useNote(id) { | |
const queryClient = useQueryClient(); | |
return useQuery( | |
['notes', 'detail', id], | |
() => fetchNote(id), | |
{ | |
initialData() { | |
// Try to get the note from the notes list query, if available | |
// Will continue the fetch() if missing | |
const state = queryClient.getData(['notes', 'list']); | |
return state?.data?.find((note) => note.id == id); | |
} | |
} | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// pages/notes.jsx | |
import { useQuery } from 'react-query'; | |
import { fetchNotes } from './lib/notes'; | |
import { useRealtime } from './lib/realtime'; | |
export default function NotesPage() { | |
// Vai re-renderizar se a lista de notas for atualizada. | |
const { data } = useNotes(); | |
useRealtime(); // habilita pra página toda | |
// ... | |
} | |
function useNotes() { | |
return useQuery(['notes', 'list'], fetchNotes); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express'); | |
const { createServer } = require('http'); | |
const { Server } = require('socket.io'); | |
const app = express(); | |
const httpServer = createServer(app); | |
const io = new Server(httpServer); | |
// app.get('/api/notes', ...); | |
// app.get('/api/notes/:id', ...); | |
app.post('/api/notes', (req, res) => { | |
// ... | |
// Descobre a empresa a partir do token e invalida a lista. | |
// Faz sentido pedir invalidação pq a ordenação pode mudar. | |
const companyRoom = getTokenIssuer(req); | |
io.to(companyRoom).emit('invalidate', ['notes', 'list']); | |
}); | |
app.patch('/api/notes/:id', (req, res) => { | |
// ... | |
// Descobre a empresa a partir do token, invalida a nota e | |
// atualiza parte da lista no lugar. | |
const companyRoom = getTokenIssuer(req); | |
io.to(companyRoom).emit('invalidate', ['notes', 'detail', updatedNote.id]); | |
io.to(companyRoom).emit('replace', ['notes', 'list'], updatedNote); | |
}); | |
io.on('connection', (socket) => { | |
const companyRoom = getTokenIssuer(socket.req); | |
if (companyRoom) socket.join(companyRoom); | |
}); | |
function getTokenIssuer(req) { | |
// Auth pode ser capturada de cookies, com a vantagem de ser incluída | |
// em todas as requisições automaticamente pelo navegador. | |
const { authorization } = req.headers; | |
if (!authorization) return null; | |
const [, token] = authorization.split(' '); | |
const { iss } = jwt.verify(token, environment.jwtSecret); | |
return iss; // cc:123 | |
} | |
httpServer.listen(3000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment