Subscriptions
Learn how to handle real-time data updates with subscriptions.
Basic Subscription
Subscriptions enable real-time communication between client and server for chat messages, notifications, live dashboards, and more.
Define a subscription like queries and mutations:
import { graphql } from '$mearie';
import { useSubscription } from '@mearie/react';
const ChatMessages = ({ chatId }: { chatId: string }) => {
const { data, error } = useSubscription(
graphql(`
subscription MessageAddedSubscription($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
content
author {
id
name
avatar
}
createdAt
}
}
`),
{ chatId },
);
return (
<div>
{data && <div>New message: {data.messageAdded.content}</div>}
{error && <div>Error: {error.message}</div>}
</div>
);
};Client Setup
Configure your client to support subscriptions.
Server-Sent Events (SSE)
Simple HTTP-based protocol. Recommended for most use cases.
import { createClient, httpExchange, subscriptionExchange } from '@mearie/react'; // or @mearie/vue, @mearie/svelte, @mearie/solid
import { createClient as createSSEClient } from 'graphql-sse';
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
httpExchange({ url: 'https://api.example.com/graphql' }),
subscriptionExchange({
client: createSSEClient({
url: 'https://api.example.com/graphql',
}),
}),
],
});TIP
Install the graphql-sse package to use Server-Sent Events:
npm install graphql-sseWebSocket
Alternative protocol with lower latency using GraphQL over WebSocket.
import { createClient, httpExchange, subscriptionExchange } from '@mearie/react'; // or @mearie/vue, @mearie/svelte, @mearie/solid
import { createClient as createWSClient } from 'graphql-ws';
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
httpExchange({ url: 'https://api.example.com/graphql' }),
subscriptionExchange({
client: createWSClient({
url: 'wss://api.example.com/graphql',
}),
}),
],
});TIP
Install the graphql-ws package to use WebSocket:
npm install graphql-wsData Ownership
data holds the most recent event and nothing else. Each event replaces the previous one wholesale — subscriptions never merge events into an accumulated value. To build up history, collect it yourself in onData (see Accumulating Events).
Every event belongs to the variables it was requested with. When variables change, Mearie tears down the stream and opens a new one: data is released, loading returns to true until the first event arrives for the new variables, and the released event stays available as previousData — data from a previous set of variables, never an earlier event under the current ones. Under skip: true the release still happens, but no stream is opened and loading stays false.
const { data, previousData } = useSubscription(
graphql(`
subscription UserStatusSubscription($userId: ID!) {
userStatus(userId: $userId) {
online
}
}
`),
{ userId },
);
// Keeps showing the last known status until the new stream emits
const status = data ?? previousData;For subscriptions, loading: true means no event has arrived for the current variables yet, not that a request is in flight — a stream that stays quiet after opening keeps reporting loading: true.
onData and onError fire only on real emissions from the server. Releasing data on a variables change is not an emission, so it never invokes either callback.
Latest State Only
Display only the current state (e.g., online status):
const UserStatus = ({ userId }: { userId: string }) => {
const { data, loading, error } = useSubscription(
graphql(`
subscription UserStatusSubscription($userId: ID!) {
userStatus(userId: $userId) {
online
lastSeen
typing
}
}
`),
{ userId },
);
if (loading) return <div>Connecting...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<span className={data.userStatus.online ? 'online' : 'offline'}>
{data.userStatus.online ? 'Online' : 'Offline'}
</span>
{data.userStatus.typing && <span>typing...</span>}
</div>
);
};Accumulating Events
Collect events over time (e.g., chat messages):
import { useState, useCallback } from 'react';
import { useSubscription, type DataOf } from '@mearie/react';
import { graphql } from '$mearie';
import type { MessageAddedSubscription } from '$mearie';
const ChatMessages = ({ chatId }: { chatId: string }) => {
const [messages, setMessages] = useState<Message[]>([]);
const handleMessageAdded = useCallback((data: DataOf<MessageAddedSubscription>) => {
setMessages((prev) => [...prev, data.messageAdded]);
}, []);
useSubscription(
graphql(`
subscription MessageAddedSubscription($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
content
author {
id
name
}
createdAt
}
}
`),
{ chatId },
{
onData: handleMessageAdded,
},
);
return (
<div>
{messages.map((message) => (
<div key={message.id}>{message.content}</div>
))}
</div>
);
};Combining with Queries
Load initial data with a query, then subscribe for updates:
import { useState, useCallback, useEffect } from 'react';
import { useQuery, useSubscription, type DataOf } from '@mearie/react';
import { graphql } from '$mearie';
import type { MessageAddedSubscription } from '$mearie';
const ChatRoom = ({ chatId }: { chatId: string }) => {
const [messages, setMessages] = useState<Message[]>([]);
// Load initial messages
const { data, loading } = useQuery(
graphql(`
query GetMessagesQuery($chatId: ID!) {
messages(chatId: $chatId) {
id
content
}
}
`),
{ chatId },
);
useEffect(() => {
if (data) {
setMessages(data.messages);
}
}, [data]);
const handleMessageAdded = useCallback((data: DataOf<MessageAddedSubscription>) => {
setMessages((prev) => [...prev, data.messageAdded]);
}, []);
// Subscribe to new messages
useSubscription(
graphql(`
subscription MessageAddedSubscription($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
content
}
}
`),
{ chatId },
{
onData: handleMessageAdded,
},
);
if (loading) return <div>Loading...</div>;
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.content}</div>
))}
</div>
);
};Subscription Options
Control subscription behavior with options:
import { useCallback } from 'react';
import { useSubscription, type DataOf } from '@mearie/react';
import { graphql } from '$mearie';
import type { MessageAddedSubscription } from '$mearie';
const handleData = useCallback((data: DataOf<MessageAddedSubscription>) => {
console.log('New message:', data.messageAdded);
}, []);
const handleError = useCallback((error: Error) => {
console.error('Subscription error:', error);
}, []);
const { data, error, loading } = useSubscription(
graphql(`
subscription MessageAddedSubscription($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
content
}
}
`),
{ chatId },
{
// Skip execution conditionally
skip: !chatId,
// Process each event
onData: handleData,
// Handle errors
onError: handleError,
},
);Best Practices
- Name subscriptions with
Subscriptionsuffix (e.g.,MessageAddedSubscription) - Load initial data with queries first, use subscriptions only for updates
- Handle connection errors gracefully
Next Steps
- Subscription Exchange - Learn more about subscription configuration
- Queries - Load initial data
- Exchanges - Learn about the exchange system