|
| 1 | +import { SerializedError } from "@reduxjs/toolkit"; |
| 2 | +import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; |
| 3 | +import { ReactElement } from "react"; |
| 4 | +import { useCreateQuery } from "./createQuery"; |
| 5 | + |
| 6 | +/** Result of a RTK useQuery hook */ |
| 7 | +export type UseQueryResult<T> = { |
| 8 | + // Base query state |
| 9 | + /** Arguments passed to the query */ |
| 10 | + originalArgs?: unknown; |
| 11 | + /** The latest returned result regardless of hook arg, if present */ |
| 12 | + data?: T; |
| 13 | + /** The latest returned result for the current hook arg, if present */ |
| 14 | + currentData?: T; |
| 15 | + /** Error result if present */ |
| 16 | + error?: unknown; |
| 17 | + /** A string generated by RTK Query */ |
| 18 | + requestId?: string; |
| 19 | + /** The name of the given endpoint for the query */ |
| 20 | + endpointName?: string; |
| 21 | + /** Timestamp for when the query was initiated */ |
| 22 | + startedTimeStamp?: number; |
| 23 | + /** Timestamp for when the query was completed */ |
| 24 | + fulfilledTimeStamp?: number; |
| 25 | + |
| 26 | + // Derived request status booleans |
| 27 | + /** Query has not started yet */ |
| 28 | + isUninitialized: boolean; |
| 29 | + /** Query is currently loading for the first time. No data yet. */ |
| 30 | + isLoading: boolean; |
| 31 | + /** Query is currently fetching, but might have data from an earlier request. */ |
| 32 | + isFetching: boolean; |
| 33 | + /** Query has data from a successful load */ |
| 34 | + isSuccess: boolean; |
| 35 | + /** Query is currently in an "error" state */ |
| 36 | + isError: boolean; |
| 37 | + |
| 38 | + /** A function to force refetch the query */ |
| 39 | + refetch: () => void; |
| 40 | +}; |
| 41 | + |
| 42 | +/** _X are types that are extended from in the generics */ |
| 43 | +export type _Q = Record<string, UseQueryResult<unknown>>; |
| 44 | +export type _D = Record<string, UseQueryResult<unknown>>; |
| 45 | +export type _E = unknown; |
| 46 | +export type _P = Record<string, unknown>; |
| 47 | +export type _R = unknown; |
| 48 | + |
| 49 | +export type MakeDataRequired<T extends _Q> = { |
| 50 | + // @ts-ignore: TS2536: Type '"data"' cannot be used to index type 'T[K]'. |
| 51 | + [K in keyof T]: T[K] & { data: NonNullable<T[K]["data"]> }; |
| 52 | +}; |
| 53 | + |
| 54 | +export type DataShape< |
| 55 | + Q extends _Q, |
| 56 | + D extends _D, |
| 57 | + E extends _E |
| 58 | +> = { |
| 59 | + queries?: Q; |
| 60 | + deferredQueries?: D; |
| 61 | + payload?: E; |
| 62 | +}; |
| 63 | + |
| 64 | +/** Use: `(...args: OptionalGenericArg<T>) => void;` |
| 65 | + * Allows either `T` or `none` for the parameter |
| 66 | + */ |
| 67 | +export type OptionalGenericArg<T> = T extends never ? [] : [T]; |
| 68 | + |
| 69 | +export type LoaderTransformFunction< |
| 70 | + Q extends _Q, |
| 71 | + D extends _D, |
| 72 | + E extends _E, |
| 73 | + R extends unknown |
| 74 | +> = (data: DataShape<MakeDataRequired<Q>, D, E>) => R; |
| 75 | + |
| 76 | +export type CreateUseLoaderArgs< |
| 77 | + Q extends _Q, |
| 78 | + D extends _D, |
| 79 | + E extends _E, |
| 80 | + R extends _R, |
| 81 | + A = never |
| 82 | +> = { |
| 83 | + /** Should return a list of RTK useQuery results. |
| 84 | + * Example: |
| 85 | + * ```typescript |
| 86 | + * (args: Args) => ({ |
| 87 | + * queries: { |
| 88 | + * pokemon: useGetPokemonQuery(args.pokemonId), |
| 89 | + * } |
| 90 | + * }) |
| 91 | + * ``` |
| 92 | + */ |
| 93 | + useQuery: ( |
| 94 | + ...args: OptionalGenericArg<A> |
| 95 | + ) => DataShape<Q, D, E>; |
| 96 | + /** Transforms the output of the queries */ |
| 97 | + transform?: (data: DataShape<Q, D, E>) => R; |
| 98 | +}; |
| 99 | + |
| 100 | +export type UseLoader<A, R> = ( |
| 101 | + ...args: OptionalGenericArg<A> |
| 102 | +) => UseQueryResult<R>; |
| 103 | + |
| 104 | +export type ComponentWithLoaderData< |
| 105 | + P extends Record<string, any>, |
| 106 | + R extends unknown |
| 107 | +> = (props: P, loaderData: R) => ReactElement; |
| 108 | + |
| 109 | +/** Use: `InferLoaderData<typeof loader>`. Returns the return-value of the given loader's aggregated query. */ |
| 110 | +export type InferLoaderData<T> = T extends Loader< |
| 111 | + any, |
| 112 | + infer X, |
| 113 | + any, |
| 114 | + any |
| 115 | +> |
| 116 | + ? X |
| 117 | + : T extends Loader<never, infer Y, any, any> |
| 118 | + ? Y |
| 119 | + : T extends Loader<any, infer Z, never, any> |
| 120 | + ? Z |
| 121 | + : never; |
| 122 | + |
| 123 | +export type Component<P extends Record<string, any>> = ( |
| 124 | + props: P |
| 125 | +) => ReactElement; |
| 126 | + |
| 127 | +export type WhileFetchingArgs< |
| 128 | + P extends unknown, |
| 129 | + R extends unknown |
| 130 | +> = { |
| 131 | + /** Will be prepended before the component while the query is fetching */ |
| 132 | + prepend?: (props: P, data?: R) => ReactElement; |
| 133 | + /** Will be appended after the component while the query is fetching */ |
| 134 | + append?: (props: P, data?: R) => ReactElement; |
| 135 | +}; |
| 136 | + |
| 137 | +export type CustomLoaderProps<T = unknown> = { |
| 138 | + /** What the loader requests be rendered while fetching data */ |
| 139 | + onFetching?: React.ReactElement; |
| 140 | + /** What the loader requests be rendered while fetching data */ |
| 141 | + whileFetching?: { |
| 142 | + /** Should be appended to the success result while fetching */ |
| 143 | + append?: React.ReactElement; |
| 144 | + /** Should be prepended to the success result while fetching */ |
| 145 | + prepend?: React.ReactElement; |
| 146 | + }; |
| 147 | + /** What the loader requests be rendered when data is available */ |
| 148 | + onSuccess: (data: T) => React.ReactElement; |
| 149 | + /** What the loader requests be rendered when the query fails */ |
| 150 | + onError?: ( |
| 151 | + error: SerializedError | FetchBaseQueryError |
| 152 | + ) => JSX.Element; |
| 153 | + /** What the loader requests be rendered while loading data */ |
| 154 | + onLoading?: React.ReactElement; |
| 155 | + /** The joined query for the loader */ |
| 156 | + query: UseQueryResult<T>; |
| 157 | +}; |
| 158 | + |
| 159 | +export type CreateLoaderArgs< |
| 160 | + P extends unknown, |
| 161 | + Q extends _Q, |
| 162 | + D extends _D, |
| 163 | + E extends _E, |
| 164 | + R extends unknown = MakeDataRequired<Q>, |
| 165 | + A = never |
| 166 | +> = Partial<CreateUseLoaderArgs<Q, D, E, R, A>> & { |
| 167 | + /** Generates an argument for the `queries` based on component props */ |
| 168 | + queriesArg?: (props: P) => A; |
| 169 | + /** Determines what to render while loading (with no data to fallback on) */ |
| 170 | + onLoading?: (props: P) => ReactElement; |
| 171 | + /** Determines what to render when query fails. */ |
| 172 | + onError?: ( |
| 173 | + props: P, |
| 174 | + error: FetchBaseQueryError | SerializedError, |
| 175 | + joinedQuery: UseQueryResult<undefined> |
| 176 | + ) => ReactElement; |
| 177 | + /** @deprecated Using onFetching might result in loss of internal state. Use `whileFetching` instead, or pass the query to the component */ |
| 178 | + onFetching?: ( |
| 179 | + props: P, |
| 180 | + renderBody: () => ReactElement |
| 181 | + ) => ReactElement; |
| 182 | + /** Determines what to render besides success-result while query is fetching. */ |
| 183 | + whileFetching?: WhileFetchingArgs<P, R>; |
| 184 | + /** The component to use to switch between rendering the different query states. */ |
| 185 | + loaderComponent?: Component<CustomLoaderProps>; |
| 186 | +}; |
| 187 | + |
| 188 | +export type Loader< |
| 189 | + P extends unknown, |
| 190 | + R extends unknown, |
| 191 | + Q extends _Q = _Q, |
| 192 | + D extends _D = _D, |
| 193 | + A = never |
| 194 | +> = { |
| 195 | + /** A hook that runs all queries and returns aggregated result */ |
| 196 | + useLoader: UseLoader<A, R>; |
| 197 | + /** Generates an argument for the `queries` based on component props */ |
| 198 | + queriesArg?: (props: P) => A; |
| 199 | + /** Determines what to render while loading (with no data to fallback on) */ |
| 200 | + onLoading?: (props: P) => ReactElement; |
| 201 | + /** Determines what to render when query fails. */ |
| 202 | + onError?: ( |
| 203 | + props: P, |
| 204 | + error: SerializedError | FetchBaseQueryError, |
| 205 | + joinedQuery: UseQueryResult<undefined> |
| 206 | + ) => ReactElement; |
| 207 | + /** @deprecated Using onFetching might result in loss of internal state. Use `whileFetching` instead, or pass the query to the component */ |
| 208 | + onFetching?: ( |
| 209 | + props: P, |
| 210 | + renderBody: () => ReactElement |
| 211 | + ) => ReactElement; |
| 212 | + /** Determines what to render besides success-result while query is fetching. */ |
| 213 | + whileFetching?: WhileFetchingArgs<P, R>; |
| 214 | + /** Returns a new `Loader` extended from this `Loader`, with given overrides. */ |
| 215 | + extend: < |
| 216 | + Qb extends _Q = Q, |
| 217 | + Db extends _D = _Q, |
| 218 | + Pb extends unknown = P, |
| 219 | + Rb extends unknown = Qb extends Q |
| 220 | + ? R extends never |
| 221 | + ? Q |
| 222 | + : R |
| 223 | + : MakeDataRequired<Qb>, |
| 224 | + Ab = A |
| 225 | + >( |
| 226 | + newLoader: Partial<CreateLoaderArgs<Pb, Qb, Db, Rb, Ab>> |
| 227 | + ) => Loader< |
| 228 | + Pb, |
| 229 | + Rb, |
| 230 | + Qb extends never ? Q : Qb, |
| 231 | + Db extends never ? D : Db, |
| 232 | + Ab |
| 233 | + >; |
| 234 | + /** The component to use to switch between rendering the different query states. */ |
| 235 | + LoaderComponent: Component<CustomLoaderProps>; |
| 236 | +}; |
| 237 | + |
| 238 | +export type CreateQueryGetter<T extends unknown> = |
| 239 | + () => Promise<T>; |
| 240 | + |
| 241 | +export type CreateQueryReducerAction<T extends unknown> = |
| 242 | + | { |
| 243 | + type: "load"; |
| 244 | + } |
| 245 | + | { |
| 246 | + type: "fetch"; |
| 247 | + } |
| 248 | + | { |
| 249 | + type: "error"; |
| 250 | + payload: { |
| 251 | + error: unknown; |
| 252 | + }; |
| 253 | + } |
| 254 | + | { |
| 255 | + type: "success"; |
| 256 | + payload: { |
| 257 | + data: T; |
| 258 | + }; |
| 259 | + }; |
| 260 | + |
| 261 | +/************************************************/ |
| 262 | +/* Legacy/unused, for backwards compatibility */ |
| 263 | +/************************************************/ |
| 264 | +export type WithLoaderArgs< |
| 265 | + P extends unknown, |
| 266 | + R extends unknown, |
| 267 | + A = never |
| 268 | +> = Loader<P, R, _Q, _Q, A>; |
| 269 | + |
| 270 | +const _createLoaderTypeTest = < |
| 271 | + P extends unknown, |
| 272 | + Q extends _Q, |
| 273 | + D extends _D, |
| 274 | + E extends _E, |
| 275 | + R extends unknown = MakeDataRequired<Q>, |
| 276 | + A = never |
| 277 | +>( |
| 278 | + args: CreateLoaderArgs<P, Q, D, E, R> |
| 279 | +): Loader<P, R, Q, D, A> => { |
| 280 | + return {} as Loader<P, R, Q, D, A>; |
| 281 | +}; |
| 282 | + |
| 283 | +const asd = _createLoaderTypeTest({ |
| 284 | + useQuery: () => { |
| 285 | + return { |
| 286 | + queries: { |
| 287 | + test: useCreateQuery(async () => "foo" as const), |
| 288 | + }, |
| 289 | + deferredQueries: { |
| 290 | + best: useCreateQuery(async () => "bar" as const), |
| 291 | + }, |
| 292 | + }; |
| 293 | + }, |
| 294 | +}); |
0 commit comments