Error handling useListeningQuery hook in custom Sanity Studio input component
I appreciate you sharing this issue, but I need to be upfront: I can't find useListeningQuery() in any official Sanity documentation. This appears to be from a third-party package that I can't verify as currently maintained or compatible with Sanity Studio 3.8.3.
Since I can't diagnose the specific errors you're seeing with an unverified hook, let me show you the official Sanity approach for handling real-time data in custom input components.
The Official Approach: Using Sanity's Listening API
According to the Listening API documentation, Sanity provides a built-in real-time event streaming API using Server-Sent Events. Combined with the useClient hook, you can build a proper listening query with full control over error handling:
import { useClient } from 'sanity';
import { useEffect, useState } from 'react';
import { Card, Spinner, Stack, Text } from '@sanity/ui';
export function MyCustomInput(props) {
const client = useClient({ apiVersion: '2024-01-01' });
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const { documentId } = props; // or however you're getting the ID
useEffect(() => {
let subscription;
try {
// Create a listening query
subscription = client
.listen(`*[_id == $documentId]`, { documentId })
.subscribe({
next: (update) => {
setData(update.result);
setLoading(false);
setError(null);
},
error: (err) => {
console.error('Listening error:', err);
setError(err);
setLoading(false);
// Connection will automatically retry
}
});
} catch (err) {
setError(err);
setLoading(false);
}
// Critical: cleanup on unmount
return () => {
if (subscription) {
subscription.unsubscribe();
}
};
}, [documentId, client]);
if (loading) return <Spinner />;
if (error) {
return (
<Card tone="critical" padding={3}>
<Stack space={2}>
<Text weight="semibold">Connection issue</Text>
<Text size={1}>
Unable to establish real-time connection.
{data && ' Showing last known data.'}
</Text>
</Stack>
</Card>
);
}
// Your main component logic
return (
<div>
{/* Your UI using data */}
</div>
);
}Why This Works Better
This approach gives you:
- Proper subscription cleanup - The
unsubscribe()call in the cleanup function prevents memory leaks and stream errors - Graceful error handling - Errors are caught without breaking the Observable stream
- Official API support - Uses documented Sanity features that will continue to work
- Automatic reconnection - The Listening API handles reconnection logic internally
About Those Error Messages
The errors you're seeing ("invalid object where a stream was expected" and "Could not establish EventSource connection") typically happen when:
- A component unmounts before properly closing the EventSource connection
- The Observable stream receives an unexpected value (like a React component) instead of stream data
- Early returns cause React to unmount the component mid-stream
The solution above avoids these issues by keeping the component mounted and using the proper cleanup pattern.
Do You Even Need This?
One more thing to consider: if you're querying the current document that your input component is part of, you probably don't need to fetch it separately. Custom input components automatically receive the document data through props - check the custom input components guide to see if the data you need is already available via props.value or other provided props.
If you can share more about what data you're trying to access and why, I might be able to suggest an even simpler approach!
Show original thread7 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.