Files
supabase/apps/ui-library/public/r/infinite-query-hook.json
3485cd4965 Docs/UI library vue nuxt password auth (#39405)
* docs: ui library password auth vue

* docs: ui library password auth nuxt

* Fix a pnpm-lock conflict.

* Adjust paths

* Fix import path

* Rebuild

* docs: remove shadcn-vue package

* Fix the pnpm-lock.

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
Co-authored-by: Terry Sutton <saltcod@gmail.com>
2025-11-10 08:27:38 -03:30

19 lines
6.7 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "infinite-query-hook",
"type": "registry:block",
"title": "Infinite Query Hook",
"description": "React hook for infinite lists, fetching data from Supabase.",
"dependencies": [
"@supabase/supabase-js",
"@supabase/postgrest-js@*"
],
"registryDependencies": [],
"files": [
{
"path": "registry/default/blocks/infinite-query-hook/hooks/use-infinite-query.ts",
"content": "'use client'\n\nimport { createClient } from '@/registry/default/fixtures/lib/supabase/client'\nimport { PostgrestQueryBuilder, type PostgrestClientOptions } from '@supabase/postgrest-js'\nimport { type SupabaseClient } from '@supabase/supabase-js'\nimport { useEffect, useRef, useSyncExternalStore } from 'react'\n\nconst supabase = createClient()\n\n// The following types are used to make the hook type-safe. It extracts the database type from the supabase client.\ntype SupabaseClientType = typeof supabase\n\n// Utility type to check if the type is any\ntype IfAny<T, Y, N> = 0 extends 1 & T ? Y : N\n\n// Extracts the database type from the supabase client. If the supabase client doesn't have a type, it will fallback properly.\ntype Database =\n SupabaseClientType extends SupabaseClient<infer U>\n ? IfAny<\n U,\n {\n public: {\n Tables: Record<string, any>\n Views: Record<string, any>\n Functions: Record<string, any>\n }\n },\n U\n >\n : {\n public: {\n Tables: Record<string, any>\n Views: Record<string, any>\n Functions: Record<string, any>\n }\n }\n\n// Change this to the database schema you want to use\ntype DatabaseSchema = Database['public']\n\n// Extracts the table names from the database type\ntype SupabaseTableName = keyof DatabaseSchema['Tables']\n\n// Extracts the table definition from the database type\ntype SupabaseTableData<T extends SupabaseTableName> = DatabaseSchema['Tables'][T]['Row']\n\n// Default client options for PostgrestQueryBuilder\ntype DefaultClientOptions = PostgrestClientOptions\n\ntype SupabaseSelectBuilder<T extends SupabaseTableName> = ReturnType<\n PostgrestQueryBuilder<\n DefaultClientOptions,\n DatabaseSchema,\n DatabaseSchema['Tables'][T],\n T\n >['select']\n>\n\n// A function that modifies the query. Can be used to sort, filter, etc. If .range is used, it will be overwritten.\ntype SupabaseQueryHandler<T extends SupabaseTableName> = (\n query: SupabaseSelectBuilder<T>\n) => SupabaseSelectBuilder<T>\n\ninterface UseInfiniteQueryProps<T extends SupabaseTableName, Query extends string = '*'> {\n // The table name to query\n tableName: T\n // The columns to select, defaults to `*`\n columns?: string\n // The number of items to fetch per page, defaults to `20`\n pageSize?: number\n // A function that modifies the query. Can be used to sort, filter, etc. If .range is used, it will be overwritten.\n trailingQuery?: SupabaseQueryHandler<T>\n}\n\ninterface StoreState<TData> {\n data: TData[]\n count: number\n isSuccess: boolean\n isLoading: boolean\n isFetching: boolean\n error: Error | null\n hasInitialFetch: boolean\n}\n\ntype Listener = () => void\n\nfunction createStore<TData extends SupabaseTableData<T>, T extends SupabaseTableName>(\n props: UseInfiniteQueryProps<T>\n) {\n const { tableName, columns = '*', pageSize = 20, trailingQuery } = props\n\n let state: StoreState<TData> = {\n data: [],\n count: 0,\n isSuccess: false,\n isLoading: false,\n isFetching: false,\n error: null,\n hasInitialFetch: false,\n }\n\n const listeners = new Set<Listener>()\n\n const notify = () => {\n listeners.forEach((listener) => listener())\n }\n\n const setState = (newState: Partial<StoreState<TData>>) => {\n state = { ...state, ...newState }\n notify()\n }\n\n const fetchPage = async (skip: number) => {\n if (state.hasInitialFetch && (state.isFetching || state.count <= state.data.length)) return\n\n setState({ isFetching: true })\n\n let query = supabase\n .from(tableName)\n .select(columns, { count: 'exact' }) as unknown as SupabaseSelectBuilder<T>\n\n if (trailingQuery) {\n query = trailingQuery(query)\n }\n const { data: newData, count, error } = await query.range(skip, skip + pageSize - 1)\n\n if (error) {\n console.error('An unexpected error occurred:', error)\n setState({ error })\n } else {\n setState({\n data: [...state.data, ...(newData as TData[])],\n count: count || 0,\n isSuccess: true,\n error: null,\n })\n }\n setState({ isFetching: false })\n }\n\n const fetchNextPage = async () => {\n if (state.isFetching) return\n await fetchPage(state.data.length)\n }\n\n const initialize = async () => {\n setState({ isLoading: true, isSuccess: false, data: [] })\n await fetchNextPage()\n setState({ isLoading: false, hasInitialFetch: true })\n }\n\n return {\n getState: () => state,\n subscribe: (listener: Listener) => {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n fetchNextPage,\n initialize,\n }\n}\n\n// Empty initial state to avoid hydration errors.\nconst initialState: any = {\n data: [],\n count: 0,\n isSuccess: false,\n isLoading: false,\n isFetching: false,\n error: null,\n hasInitialFetch: false,\n}\n\nfunction useInfiniteQuery<\n TData extends SupabaseTableData<T>,\n T extends SupabaseTableName = SupabaseTableName,\n>(props: UseInfiniteQueryProps<T>) {\n const storeRef = useRef(createStore<TData, T>(props))\n\n const state = useSyncExternalStore(\n storeRef.current.subscribe,\n () => storeRef.current.getState(),\n () => initialState as StoreState<TData>\n )\n\n useEffect(() => {\n // Recreate store if props change\n if (\n storeRef.current.getState().hasInitialFetch &&\n (props.tableName !== props.tableName ||\n props.columns !== props.columns ||\n props.pageSize !== props.pageSize)\n ) {\n storeRef.current = createStore<TData, T>(props)\n }\n\n if (!state.hasInitialFetch && typeof window !== 'undefined') {\n storeRef.current.initialize()\n }\n }, [props.tableName, props.columns, props.pageSize, state.hasInitialFetch])\n\n return {\n data: state.data,\n count: state.count,\n isSuccess: state.isSuccess,\n isLoading: state.isLoading,\n isFetching: state.isFetching,\n error: state.error,\n hasMore: state.count > state.data.length,\n fetchNextPage: storeRef.current.fetchNextPage,\n }\n}\n\nexport {\n useInfiniteQuery,\n type SupabaseQueryHandler,\n type SupabaseTableData,\n type SupabaseTableName,\n type UseInfiniteQueryProps,\n}\n",
"type": "registry:hook"
}
]
}