@@ -28,6 +28,8 @@ import {
2828 Logger ,
2929} from "../browser/logging.js" ;
3030import { ConvexQueryOptions } from "../browser/query_options.js" ;
31+ import type { Preloaded } from "./hydration.js" ;
32+ import { parsePreloaded } from "./preloaded_utils.js" ;
3133
3234// When no arguments are passed, extend subscriptions (for APIs that do this by default)
3335// for this amount after the subscription would otherwise be dropped.
@@ -642,6 +644,95 @@ export type OptionalRestArgsOrSkip<FuncRef extends FunctionReference<any>> =
642644 ? [ args ?: EmptyObject | "skip" ]
643645 : [ args : FuncRef [ "_args" ] | "skip" ] ;
644646
647+ /**
648+ * Options for the object-based {@link useQuery} overload.
649+ *
650+ * @public
651+ */
652+ export type UseQueryOptions < Query extends FunctionReference < "query" > > = {
653+ /**
654+ * The query function to run.
655+ */
656+ query : Query ;
657+ /**
658+ * Whether to throw an error if the query fails.
659+ * If false, the error will be returned in the `error` field.
660+ * @defaultValue false
661+ */
662+ throwOnError ?: boolean ;
663+ /**
664+ * An initial value to use before the query result is available.
665+ * @defaultValue undefined
666+ */
667+ initialValue ?: Query [ "_returnType" ] ;
668+ /**
669+ * When true, the query will not be subscribed and it will behave the same as
670+ * if it was loading.
671+ * @defaultValue false
672+ */
673+ skip ?: boolean ;
674+ } & ( FunctionArgs < Query > extends EmptyObject
675+ ? {
676+ /**
677+ * The arguments to the query function.
678+ * Optional for queries with no arguments.
679+ */
680+ args ?: FunctionArgs < Query > ;
681+ }
682+ : {
683+ /**
684+ * The arguments to the query function.
685+ */
686+ args : FunctionArgs < Query > ;
687+ } ) ;
688+
689+ /**
690+ * Options for the object-based {@link useQuery} overload with a preloaded query.
691+ *
692+ * @public
693+ */
694+ export type UseQueryPreloadedOptions < Query extends FunctionReference < "query" > > =
695+ {
696+ /**
697+ * A preloaded query result from a Server Component.
698+ */
699+ preloaded : Preloaded < Query > ;
700+ /**
701+ * Whether to throw an error if the query fails.
702+ * If false, the error will be returned in the `error` field.
703+ * @defaultValue false
704+ */
705+ throwOnError ?: boolean ;
706+ /**
707+ * When true, the query will not be subscribed and it will behave the same as
708+ * if it was loading.
709+ * @defaultValue false
710+ */
711+ skip ?: boolean ;
712+ } ;
713+
714+ /**
715+ * Result type for the object-based {@link useQuery} overload.
716+ *
717+ * @public
718+ */
719+ export type UseQueryResult < T > =
720+ | {
721+ status : "success" ;
722+ value : T ;
723+ error : undefined ;
724+ }
725+ | {
726+ status : "error" ;
727+ value : undefined ;
728+ error : Error ;
729+ }
730+ | {
731+ status : "loading" ;
732+ value : undefined ;
733+ error : undefined ;
734+ } ;
735+
645736/**
646737 * Load a reactive query within a React component.
647738 *
@@ -661,20 +752,84 @@ export type OptionalRestArgsOrSkip<FuncRef extends FunctionReference<any>> =
661752export function useQuery < Query extends FunctionReference < "query" > > (
662753 query : Query ,
663754 ...args : OptionalRestArgsOrSkip < Query >
664- ) : Query [ "_returnType" ] | undefined {
665- const skip = args [ 0 ] === "skip" ;
666- const argsObject = args [ 0 ] === "skip" ? { } : parseArgs ( args [ 0 ] ) ;
755+ ) : Query [ "_returnType" ] | undefined ;
667756
668- const queryReference =
669- typeof query === "string"
670- ? makeFunctionReference < "query" , any , any > ( query )
671- : query ;
757+ /**
758+ * Load a reactive query within a React component using an options object.
759+ *
760+ * This overload returns an object with `status`, `error`, and `value` fields
761+ * instead of throwing errors or returning undefined.
762+ *
763+ * This React hook contains internal state that will cause a rerender
764+ * whenever the query result changes.
765+ *
766+ * Throws an error if not used under {@link ConvexProvider}.
767+ *
768+ * @param options - An options object or the string "skip" to skip the query.
769+ * @returns An object with `status`, `error`, and `value` fields.
770+ *
771+ * @public
772+ */
773+ export function useQuery < Query extends FunctionReference < "query" > > (
774+ options : UseQueryOptions < Query > | UseQueryPreloadedOptions < Query > | "skip" ,
775+ ) : UseQueryResult < Query [ "_returnType" ] > ;
672776
673- const queryName = getFunctionName ( queryReference ) ;
777+ export function useQuery < Query extends FunctionReference < "query" > > (
778+ queryOrOptions :
779+ | Query
780+ | UseQueryOptions < Query >
781+ | UseQueryPreloadedOptions < Query >
782+ | "skip" ,
783+ ...args : OptionalRestArgsOrSkip < Query >
784+ ) : Query [ "_returnType" ] | undefined | UseQueryResult < Query [ "_returnType" ] > {
785+ const isObjectOptions =
786+ typeof queryOrOptions === "object" &&
787+ queryOrOptions !== null &&
788+ ( "query" in queryOrOptions || "preloaded" in queryOrOptions ) ;
789+ const isObjectSkip =
790+ queryOrOptions === "skip" || ( isObjectOptions && ! ! queryOrOptions . skip ) ;
791+ const isLegacy = ! isObjectOptions && ! isObjectSkip ;
792+ const legacySkip = isLegacy && args [ 0 ] === "skip" ;
793+ const isObjectReturn = isObjectOptions || isObjectSkip ;
794+
795+ let queryReference : Query | undefined ;
796+ let argsObject : Record < string , Value > = { } ;
797+ let throwOnError = false ;
798+ let initialValue : Query [ "_returnType" ] | undefined ;
799+ let preloadedResult : Query [ "_returnType" ] | undefined ;
800+
801+ if ( isObjectOptions ) {
802+ if ( "preloaded" in queryOrOptions ) {
803+ const parsed = parsePreloaded ( queryOrOptions . preloaded ) ;
804+ queryReference = parsed . queryReference ;
805+ argsObject = parsed . argsObject ;
806+ preloadedResult = parsed . preloadedResult ;
807+ throwOnError = queryOrOptions . throwOnError ?? false ;
808+ } else {
809+ const query = queryOrOptions . query ;
810+ queryReference =
811+ typeof query === "string"
812+ ? ( makeFunctionReference < "query" , any , any > ( query ) as Query )
813+ : query ;
814+ argsObject = queryOrOptions . args ?? ( { } as Record < string , Value > ) ;
815+ throwOnError = queryOrOptions . throwOnError ?? false ;
816+ initialValue = queryOrOptions . initialValue ;
817+ }
818+ } else if ( isLegacy ) {
819+ const query = queryOrOptions as Query ;
820+ queryReference =
821+ typeof query === "string"
822+ ? ( makeFunctionReference < "query" , any , any > ( query ) as Query )
823+ : query ;
824+ argsObject = legacySkip ? { } : parseArgs ( args [ 0 ] as Query [ "_args" ] ) ;
825+ }
826+
827+ const skip = isObjectSkip || legacySkip ;
828+ const queryName = queryReference ? getFunctionName ( queryReference ) : "" ;
674829
675830 const queries = useMemo (
676831 ( ) =>
677- skip
832+ skip || ! queryReference
678833 ? ( { } as RequestForQueries )
679834 : { query : { query : queryReference , args : argsObject } } ,
680835 // Stringify args so args that are semantically the same don't trigger a
@@ -685,10 +840,46 @@ export function useQuery<Query extends FunctionReference<"query">>(
685840
686841 const results = useQueries ( queries ) ;
687842 const result = results [ "query" ] ;
843+
844+ if ( ! isObjectReturn ) {
845+ if ( result instanceof Error ) {
846+ throw result ;
847+ }
848+ return result ;
849+ }
850+
688851 if ( result instanceof Error ) {
689- throw result ;
852+ if ( throwOnError ) {
853+ throw result ;
854+ }
855+ return {
856+ status : "error" ,
857+ value : undefined ,
858+ error : result ,
859+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
690860 }
691- return result ;
861+
862+ if ( result === undefined ) {
863+ const fallbackValue = preloadedResult ?? initialValue ;
864+ if ( fallbackValue !== undefined ) {
865+ return {
866+ status : "success" ,
867+ value : fallbackValue ,
868+ error : undefined ,
869+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
870+ }
871+ return {
872+ status : "loading" ,
873+ value : undefined ,
874+ error : undefined ,
875+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
876+ }
877+
878+ return {
879+ status : "success" ,
880+ value : result ,
881+ error : undefined ,
882+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
692883}
693884
694885/**
0 commit comments