Una minoría, pero aún una gran cantidad de proyectos, requieren la integración de sockets web para proporcionar una reacción instantánea de una interfaz ante los cambios sin tener que volver a buscar datos. Es algo esencial y no vamos a hablar de ello ni a hacer una comparación entre bibliotecas de terceros que proporcionan API para una mejor experiencia de desarrollo. Mi objetivo es mostrar cómo integrar rápidamente con NextJs. Y cómo resolver los problemas que enfrentamos durante el desarrollo. @microsoft/signalr Espero que todos ya hayan instalado e implementado el proyecto NextJS localmente. En mi caso la versión es . Agreguemos algunas bibliotecas más importantes: (versión ) para recuperar datos y trabajar más con el caché local y (versión ) - API para sockets web. 13.2.4 swr 2.1.5 @microsoft/signalr 7.0.5 npm install --save @microsoft/signalr swr Comencemos creando una función simple y un nuevo enlace llamado para obtener datos iniciales de nuestra API REST. Devuelve una lista de los mensajes del chat, campos que detectan errores y estado de carga, y el método que permite cambiar los datos almacenados en caché. fetcher useChatData mutate // hooks/useChatData.ts import useSWR from 'swr'; type Message = { content: string; createdAt: Date; id: string; }; async function fetcher<TResponse>(url: string, config: RequestInit): Promise<TResponse> { const response = await fetch(url, config); if (!response.ok) { throw response; } return await response.json(); } export const useChatData = () => { const { data, error, isLoading, mutate } = useSWR<Message[]>('OUR_API_URL', fetcher); return { data: data || [], isLoading, isError: error, mutate, }; }; Para probar que funciona como se supone, actualicemos el componente de nuestra página. Importe nuestro gancho en la parte superior y extraiga datos de él como en el fragmento a continuación. Si funciona, verá los datos renderizados. Como ves, es bastante sencillo. // pages/chat.ts import { useChatData } from 'hooks/useChatData'; const Chat: NextPage = () => { const { data } = useChatData(); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; El siguiente paso requiere conectar nuestra página futura a sockets web, capturar eventos y actualizar un caché con un mensaje nuevo. Propongo comenzar construyendo el servicio de socket en un archivo separado. NewMessage Según los ejemplos de los documentos de SignalR, tenemos que crear una instancia de conexión para escuchar más eventos. También agregué un objeto de conexiones para evitar duplicados y dos ayudantes para iniciar/detener las conexiones. // api/socket.ts import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; let connections = {} as { [key: string]: { type: string; connection: HubConnection; started: boolean } }; function createConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj) { console.log('SOCKET: Registering on server events ', messageType); const connection = new HubConnectionBuilder() .withUrl('API_URL', { logger: LogLevel.Information, withCredentials: false, }) .withAutomaticReconnect() .build(); connections[messageType] = { type: messageType, connection: connection, started: false, }; return connection; } else { return connections[messageType].connection; } } function startConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj.started) { connectionObj.connection.start().catch(err => console.error('SOCKET: ', err.toString())); connectionObj.started = true; } } function stopConnection(messageType: string) { const connectionObj = connections[messageType]; if (connectionObj) { console.log('SOCKET: Stoping connection ', messageType); connectionObj.connection.stop(); connectionObj.started = false; } } function registerOnServerEvents( messageType: string, callback: (payload: Message) => void, ) { try { const connection = createConnection(messageType); connection.on('NewIncomingMessage', (payload: Message) => { callback(payload); }); connection.onclose(() => stopConnection(messageType)); startConnection(messageType); } catch (error) { console.error('SOCKET: ', error); } } export const socketService = { registerOnServerEvents, stopConnection, }; Ahora nuestra página podría verse como en el fragmento de código. Recuperamos y extraemos con la lista de mensajes y los representamos. Además, anterior registra el evento , crea una conexión y escucha el backend. data useEffect NewMessage Cuando se activa el evento, el método del gancho actualiza la lista existente con un nuevo objeto. mutate // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; Me parece bien, funciona y vemos como aparecen nuevos mensajes en el feed. Elegí el ejemplo básico con chat porque es claro y fácil de entender. Y, por supuesto, lo aplicas según tu propia lógica. Pequeño bono Al utilizar una de las versiones ( ), nos enfrentamos a un problema de duplicaciones. Estaba conectado a , la matriz de dependencia. Cada vez que se cambió la dependencia, devolución de llamada en caché y la activó una y otra vez. @microsoft/signalr useEffect connection.on(event, callback); useEffect(() => { // data equals [] by default (registerOnServerEvents 1 run), // but after initial data fetching it changes (registerOnServerEvents 2 run) // each event changes data and triggers runnning of registerOnServerEvents socketService.registerOnServerEvents( 'NewMessage', // callback cached (payload: Message) => { // mutate called multiple times on each data change mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); // after getting 3 messages events, we had got 4 messages rendered lol La solución más rápida y confiable que encontramos fue mantener una copia de los datos dentro de React y usarla dentro de para futuras actualizaciones. ref useEffect // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); const messagesRef = useRef<Message[]>([]); useEffect(() => { messagesRef.current = chatData; }, [chatData]); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { const messagesCopy = messagesRef.current.slice(); mutate(() => [...messagesCopy, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; Actualmente utilizamos una nueva versión de que parece que ya tiene las correcciones necesarias. Pero de todos modos, si alguien encuentra útil esta solución y la utiliza, estaré feliz. Para concluir, quiero decir que mi experiencia con SignalR es bastante positiva, la instalación no requirió dependencias ni configuraciones específicas, funciona bien y cubre nuestras necesidades. @microsoft/signalr