Error handling useListeningQuery hook in custom Sanity Studio input component

7 replies
Last updated: May 5, 2023
Hi Sanity Folk,I’m using
user T
’s (very convenient!) useListeningQuery() hook to build a custom input component. I use it to grab some data from the Content Lake when the component loads, and to update values when they change (either from the current author’s, or someone else’s input). It’s all working fine … except when I try to catch errors due to network unavailability. If I return a component (as User models in the repo), I get an
Error: Could not establish EventSource connection
and an additional
TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable
message in the console. The component also runs continuously.Here’s the relevant code (I’m in Studio version 3.8.3):


	// Get the videoId and playerType from the document & listen for changes
	const { data, loading, error }: { data: any; loading: boolean; error: any } = useListeningQuery<SanityDocument[]>(
		`*[_id == $documentId]`,
		{
			params: { documentId },
			initialValue: [],
		},
	);
	if (loading) return <Spinner />;

	// ⬇ TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.
	// ⬇ Error: Could not establish EventSource connection
	if (error) {
		console.log('error: ', error);
		return <Card tone="critical">There has been a problem</Card>;
	}
Any ideas for how to handle that potential error state more gracefully?
Thanks!
AI Update

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:

  1. Proper subscription cleanup - The unsubscribe() call in the cleanup function prevents memory leaks and stream errors
  2. Graceful error handling - Errors are caught without breaking the Observable stream
  3. Official API support - Uses documented Sanity features that will continue to work
  4. 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 thread
7 replies
I can probably give you a better example for handling the error. How are you simulating network availability?
Great! I’m just toggling to “Offline” in the “Throttling” menu in the Network tab of Chrome Dev Tools once the page is loaded.
I’ll take a look tomorrow! 🤞
Fantastic. Thank you!
Hey User,
I’m pushing a new version of the package now which has better error handling, it will stop retrying the query in the event of an error. This means however you’d need to build your own mechanism to
reload the component in case of an error.
The error message you’re mentioning comes from the documentStore itself – which this hook is just a convenient wrapper around. So there’s nothing more this hook could do to prevent or handle it.
Great! Thanks User. I’ll try that out.
That did what I needed — thanks User!

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.

Was this answer helpful?