@@ -38,6 +38,8 @@ import {
3838 PaginatedQueryClient ,
3939 ExtendedTransition ,
4040} from "../browser/sync/paginated_query_client.js" ;
41+ import type { Preloaded } from "./hydration.js" ;
42+ import { parsePreloaded } from "./preloaded_utils.js" ;
4143
4244// When no arguments are passed, extend subscriptions (for APIs that do this by default)
4345// for this amount after the subscription would otherwise be dropped.
@@ -801,6 +803,95 @@ export type OptionalRestArgsOrSkip<FuncRef extends FunctionReference<any>> =
801803 ? [ args ?: EmptyObject | "skip" ]
802804 : [ args : FuncRef [ "_args" ] | "skip" ] ;
803805
806+ /**
807+ * Options for the object-based {@link useQuery} overload.
808+ *
809+ * @public
810+ */
811+ export type UseQueryOptions < Query extends FunctionReference < "query" > > = {
812+ /**
813+ * The query function to run.
814+ */
815+ query : Query ;
816+ /**
817+ * Whether to throw an error if the query fails.
818+ * If false, the error will be returned in the `error` field.
819+ * @defaultValue false
820+ */
821+ throwOnError ?: boolean ;
822+ /**
823+ * An initial value to use before the query result is available.
824+ * @defaultValue undefined
825+ */
826+ initialValue ?: Query [ "_returnType" ] ;
827+ /**
828+ * When true, the query will not be subscribed and it will behave the same as
829+ * if it was loading.
830+ * @defaultValue false
831+ */
832+ skip ?: boolean ;
833+ } & ( FunctionArgs < Query > extends EmptyObject
834+ ? {
835+ /**
836+ * The arguments to the query function.
837+ * Optional for queries with no arguments.
838+ */
839+ args ?: FunctionArgs < Query > ;
840+ }
841+ : {
842+ /**
843+ * The arguments to the query function.
844+ */
845+ args : FunctionArgs < Query > ;
846+ } ) ;
847+
848+ /**
849+ * Options for the object-based {@link useQuery} overload with a preloaded query.
850+ *
851+ * @public
852+ */
853+ export type UseQueryPreloadedOptions < Query extends FunctionReference < "query" > > =
854+ {
855+ /**
856+ * A preloaded query result from a Server Component.
857+ */
858+ preloaded : Preloaded < Query > ;
859+ /**
860+ * Whether to throw an error if the query fails.
861+ * If false, the error will be returned in the `error` field.
862+ * @defaultValue false
863+ */
864+ throwOnError ?: boolean ;
865+ /**
866+ * When true, the query will not be subscribed and it will behave the same as
867+ * if it was loading.
868+ * @defaultValue false
869+ */
870+ skip ?: boolean ;
871+ } ;
872+
873+ /**
874+ * Result type for the object-based {@link useQuery} overload.
875+ *
876+ * @public
877+ */
878+ export type UseQueryResult < T > =
879+ | {
880+ status : "success" ;
881+ value : T ;
882+ error : undefined ;
883+ }
884+ | {
885+ status : "error" ;
886+ value : undefined ;
887+ error : Error ;
888+ }
889+ | {
890+ status : "loading" ;
891+ value : undefined ;
892+ error : undefined ;
893+ } ;
894+
804895/**
805896 * Load a reactive query within a React component.
806897 *
@@ -820,20 +911,84 @@ export type OptionalRestArgsOrSkip<FuncRef extends FunctionReference<any>> =
820911export function useQuery < Query extends FunctionReference < "query" > > (
821912 query : Query ,
822913 ...args : OptionalRestArgsOrSkip < Query >
823- ) : Query [ "_returnType" ] | undefined {
824- const skip = args [ 0 ] === "skip" ;
825- const argsObject = args [ 0 ] === "skip" ? { } : parseArgs ( args [ 0 ] ) ;
914+ ) : Query [ "_returnType" ] | undefined ;
826915
827- const queryReference =
828- typeof query === "string"
829- ? makeFunctionReference < "query" , any , any > ( query )
830- : query ;
916+ /**
917+ * Load a reactive query within a React component using an options object.
918+ *
919+ * This overload returns an object with `status`, `error`, and `value` fields
920+ * instead of throwing errors or returning undefined.
921+ *
922+ * This React hook contains internal state that will cause a rerender
923+ * whenever the query result changes.
924+ *
925+ * Throws an error if not used under {@link ConvexProvider}.
926+ *
927+ * @param options - An options object or the string "skip" to skip the query.
928+ * @returns An object with `status`, `error`, and `value` fields.
929+ *
930+ * @public
931+ */
932+ export function useQuery < Query extends FunctionReference < "query" > > (
933+ options : UseQueryOptions < Query > | UseQueryPreloadedOptions < Query > | "skip" ,
934+ ) : UseQueryResult < Query [ "_returnType" ] > ;
935+
936+ export function useQuery < Query extends FunctionReference < "query" > > (
937+ queryOrOptions :
938+ | Query
939+ | UseQueryOptions < Query >
940+ | UseQueryPreloadedOptions < Query >
941+ | "skip" ,
942+ ...args : OptionalRestArgsOrSkip < Query >
943+ ) : Query [ "_returnType" ] | undefined | UseQueryResult < Query [ "_returnType" ] > {
944+ const isObjectOptions =
945+ typeof queryOrOptions === "object" &&
946+ queryOrOptions !== null &&
947+ ( "query" in queryOrOptions || "preloaded" in queryOrOptions ) ;
948+ const isObjectSkip =
949+ queryOrOptions === "skip" || ( isObjectOptions && ! ! queryOrOptions . skip ) ;
950+ const isLegacy = ! isObjectOptions && ! isObjectSkip ;
951+ const legacySkip = isLegacy && args [ 0 ] === "skip" ;
952+ const isObjectReturn = isObjectOptions || isObjectSkip ;
953+
954+ let queryReference : Query | undefined ;
955+ let argsObject : Record < string , Value > = { } ;
956+ let throwOnError = false ;
957+ let initialValue : Query [ "_returnType" ] | undefined ;
958+ let preloadedResult : Query [ "_returnType" ] | undefined ;
959+
960+ if ( isObjectOptions ) {
961+ if ( "preloaded" in queryOrOptions ) {
962+ const parsed = parsePreloaded ( queryOrOptions . preloaded ) ;
963+ queryReference = parsed . queryReference ;
964+ argsObject = parsed . argsObject ;
965+ preloadedResult = parsed . preloadedResult ;
966+ throwOnError = queryOrOptions . throwOnError ?? false ;
967+ } else {
968+ const query = queryOrOptions . query ;
969+ queryReference =
970+ typeof query === "string"
971+ ? ( makeFunctionReference < "query" , any , any > ( query ) as Query )
972+ : query ;
973+ argsObject = queryOrOptions . args ?? ( { } as Record < string , Value > ) ;
974+ throwOnError = queryOrOptions . throwOnError ?? false ;
975+ initialValue = queryOrOptions . initialValue ;
976+ }
977+ } else if ( isLegacy ) {
978+ const query = queryOrOptions as Query ;
979+ queryReference =
980+ typeof query === "string"
981+ ? ( makeFunctionReference < "query" , any , any > ( query ) as Query )
982+ : query ;
983+ argsObject = legacySkip ? { } : parseArgs ( args [ 0 ] as Query [ "_args" ] ) ;
984+ }
831985
832- const queryName = getFunctionName ( queryReference ) ;
986+ const skip = isObjectSkip || legacySkip ;
987+ const queryName = queryReference ? getFunctionName ( queryReference ) : "" ;
833988
834989 const queries = useMemo (
835990 ( ) =>
836- skip
991+ skip || ! queryReference
837992 ? ( { } as RequestForQueries )
838993 : { query : { query : queryReference , args : argsObject } } ,
839994 // Stringify args so args that are semantically the same don't trigger a
@@ -844,10 +999,46 @@ export function useQuery<Query extends FunctionReference<"query">>(
844999
8451000 const results = useQueries ( queries ) ;
8461001 const result = results [ "query" ] ;
1002+
1003+ if ( ! isObjectReturn ) {
1004+ if ( result instanceof Error ) {
1005+ throw result ;
1006+ }
1007+ return result ;
1008+ }
1009+
8471010 if ( result instanceof Error ) {
848- throw result ;
1011+ if ( throwOnError ) {
1012+ throw result ;
1013+ }
1014+ return {
1015+ status : "error" ,
1016+ value : undefined ,
1017+ error : result ,
1018+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
8491019 }
850- return result ;
1020+
1021+ if ( result === undefined ) {
1022+ const fallbackValue = preloadedResult ?? initialValue ;
1023+ if ( fallbackValue !== undefined ) {
1024+ return {
1025+ status : "success" ,
1026+ value : fallbackValue ,
1027+ error : undefined ,
1028+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
1029+ }
1030+ return {
1031+ status : "loading" ,
1032+ value : undefined ,
1033+ error : undefined ,
1034+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
1035+ }
1036+
1037+ return {
1038+ status : "success" ,
1039+ value : result ,
1040+ error : undefined ,
1041+ } satisfies UseQueryResult < Query [ "_returnType" ] > ;
8511042}
8521043
8531044/**
0 commit comments