diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index c23267a..41ae2d2 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -39,8 +39,9 @@ display: none; } .icon-npm::after { + background-color: rgb(55, 55, 55); background-image: url("https://img.shields.io/npm/v/@ryfylke-react/rtk-query-loader?color=gray&style=flat-square"); - width: 88px; + width: 80px; height: 20px; content: ""; display: flex; diff --git a/package.json b/package.json index 5e9aa38..8d6aa3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ryfylke-react/rtk-query-loader", - "version": "1.0.0", + "version": "1.0.1-beta.0", "description": "Lets you create reusable, extendable RTK loaders for React components.", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/createLoader.ts b/src/createLoader.ts index 846c5ca..0f9ffc3 100644 --- a/src/createLoader.ts +++ b/src/createLoader.ts @@ -3,23 +3,37 @@ import { RTKLoader } from "./RTKLoader"; import * as Types from "./types"; export const createUseLoader = < - Q extends Types._Q, - D extends Types._D, - E extends Types._E, - R extends unknown = Types.ResolveDataShape< - Types.MakeDataRequired, - D, - E + TQueries extends Types._TQueries, + TDeferred extends Types._TDeferred, + TPayload extends Types._TPayload, + TReturn extends unknown = Types.ResolveDataShape< + Types.MakeDataRequired, + TDeferred, + TPayload >, - A = never + TArg = never >( - createUseLoaderArgs: Types.CreateUseLoaderArgs -): Types.UseLoader => { - const useLoader = (...args: Types.OptionalGenericArg) => { + createUseLoaderArgs: Types.CreateUseLoaderArgs< + TQueries, + TDeferred, + TPayload, + TReturn, + TArg + > +): Types.UseLoader< + TArg, + TReturn, + TQueries, + TDeferred, + TPayload +> => { + const useLoader = ( + ...args: Types.OptionalGenericArg + ) => { const loaderRes = createUseLoaderArgs.useQueries(...args); const queriesList = loaderRes.queries ? Object.keys(loaderRes.queries).map( - (key) => (loaderRes.queries as Q)[key] + (key) => (loaderRes.queries as TQueries)[key] ) : []; const aggregatedQuery = aggregateToQuery(queriesList); @@ -28,9 +42,9 @@ export const createUseLoader = < const data = createUseLoaderArgs.transform ? createUseLoaderArgs.transform( loaderRes as Types.ResolveDataShape< - Types.MakeDataRequired, - D, - E + Types.MakeDataRequired, + TDeferred, + TPayload > ) : loaderRes; @@ -41,10 +55,10 @@ export const createUseLoader = < data, currentData: data, originalArgs: args, - } as Types.UseQueryResult; + } as Types.UseQueryResult; } - return aggregatedQuery as Types.UseQueryResult; + return aggregatedQuery as Types.UseQueryResult; }; useLoader.original_args = createUseLoaderArgs; @@ -52,27 +66,48 @@ export const createUseLoader = < }; export const createLoader = < - P extends unknown, - Q extends Types._Q, - D extends Types._D, - E extends Types._E, - R extends unknown = Types.ResolveDataShape< - Types.MakeDataRequired, - D, - E + TProps extends unknown, + TQueries extends Types._TQueries = never, + TDeferred extends Types._TDeferred = never, + TPayload extends Types._TPayload = never, + TReturn extends unknown = Types.ResolveDataShape< + Types.MakeDataRequired, + TDeferred, + TPayload >, - A extends unknown = never + TArg extends unknown = never >( - createLoaderArgs: Types.CreateLoaderArgs -): Types.Loader => { + createLoaderArgs: Types.CreateLoaderArgs< + TProps, + TQueries, + TDeferred, + TPayload, + TReturn, + TArg + > +): Types.Loader< + TProps, + TReturn, + TQueries, + TDeferred, + TPayload, + TArg +> => { const useLoader = createUseLoader({ useQueries: createLoaderArgs.useQueries ?? - (() => ({} as unknown as Q)), + (() => ({} as unknown as TQueries)), transform: createLoaderArgs.transform, }); - const loader: Types.Loader = { + const loader: Types.Loader< + TProps, + TReturn, + TQueries, + TDeferred, + TPayload, + TArg + > = { useLoader, onLoading: createLoaderArgs.onLoading, onError: createLoaderArgs.onError, @@ -82,34 +117,57 @@ export const createLoader = < LoaderComponent: createLoaderArgs.loaderComponent ?? RTKLoader, extend: function < - Qb extends Types._Q, - Db extends Types._D, - Eb extends Types._E, - Pb extends unknown = P, - Rb = Qb extends unknown - ? R - : Types.ResolveDataShape< - Types.MakeDataRequired, - Db, - Eb + E_TQueries extends Types._TQueries = TQueries, + E_TDeferred extends Types._TDeferred = TDeferred, + E_TPayload extends Types._TPayload = TPayload, + E_TReturn extends unknown = Types.AllEql< + TQueries, + E_TQueries, + TDeferred, + E_TDeferred, + TPayload, + E_TPayload + > extends true + ? TReturn + : Types.ResolveLoadedDataShape< + E_TQueries, + E_TDeferred, + E_TPayload >, - Ab extends unknown = A + E_TProps extends unknown = TProps, + E_TArg = TArg >({ useQueries, transform, ...loaderArgs - }: Partial>) { - const extendedLoader = { + }: Partial< + Types.CreateLoaderArgs< + E_TProps, + E_TQueries, + E_TDeferred, + E_TPayload, + E_TReturn, + E_TArg + > + >) { + const extendedLoader: Types.Loader< + E_TProps, + E_TReturn, + E_TQueries, + E_TDeferred, + E_TPayload, + E_TArg + > = { ...(this as unknown as Types.Loader< - Pb, - Rb, - Qb, - Db, - Eb, - Ab + E_TProps, + E_TReturn, + E_TQueries, + E_TDeferred, + E_TPayload, + E_TArg >), ...loaderArgs, - } as Types.Loader; + }; if (useQueries) { const newUseLoader = createUseLoader({ diff --git a/src/index.ts b/src/index.ts index 8e8bd36..02083d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,10 @@ export { RTKLoader } from "./RTKLoader"; export type { Component, ComponentWithLoaderData, + ConsumerProps, CreateUseLoaderArgs, CustomLoaderProps, + DataShapeInput, InferLoaderData, Loader, LoaderTransformFunction, diff --git a/src/types.ts b/src/types.ts index 9ecc757..6177272 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,30 @@ import { SerializedError } from "@reduxjs/toolkit"; import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; import { ReactElement } from "react"; +export type AllNever< + TQueries, + TDeferred, + TPayload, + TReturn = never +> = TQueries | TDeferred | TPayload | TReturn extends never + ? true + : false; + +export type AllEql< + TQueries, + E_TQueries, + TDeferred, + E_TDeferred, + TPayload, + E_TPayload +> = TQueries extends E_TQueries + ? TDeferred extends E_TDeferred + ? TPayload extends E_TPayload + ? true + : false + : false + : false; + /** Result of a RTK useQuery hook */ export type UseQueryResult = { // Base query state @@ -39,48 +63,50 @@ export type UseQueryResult = { }; /** _X are types that are extended from in the generics */ -export type _Q = Record>; -export type _D = Record>; -export type _E = unknown; -export type _P = Record; -export type _R = unknown; +export type _TQueries = + | Record> + | never; +export type _TDeferred = + | Record> + | never; +export type _TPayload = unknown | never; +export type _TProps = Record; +export type _TReturn = unknown; -export type MakeDataRequired = { +export type MakeDataRequired = { // @ts-ignore: TS2536: Type '"data"' cannot be used to index type 'T[K]'. [K in keyof T]: T[K] & { data: NonNullable }; }; -export type DataShape< - Q extends _Q, - D extends _D, - E extends _E +export type DataShapeInput< + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload > = { - queries?: Q; - deferredQueries?: D; - payload?: E; + queries?: TQueries; + deferredQueries?: TDeferred; + payload?: TPayload; }; export type ResolveDataShape< - Q extends _Q, - D extends _D, - E extends _E -> = Q extends never - ? D extends never - ? E extends never - ? never - : { payload: E } - : E extends never - ? { deferredQueries: D } - : { deferredQueries: D; payload: E } - : D extends never - ? E extends never - ? { queries: Q } - : { queries: Q; payload: E } - : E extends never - ? D extends never - ? { queries: Q } - : { queries: Q; deferredQueries: D } - : { queries: Q; deferredQueries: D; payload: E }; + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload +> = { + queries: TQueries extends never ? never : TQueries; + deferredQueries: TDeferred extends never ? never : TDeferred; + payload: TPayload extends never ? never : TPayload; +}; + +export type ResolveLoadedDataShape< + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload +> = ResolveDataShape< + MakeDataRequired, + TDeferred, + TPayload +>; /** Use: `(...args: OptionalGenericArg) => void;` * Allows either `T` or `none` for the parameter @@ -88,18 +114,20 @@ export type ResolveDataShape< export type OptionalGenericArg = T extends never ? [] : [T]; export type LoaderTransformFunction< - Q extends _Q, - D extends _D, - E extends _E, - R extends unknown -> = (data: ResolveDataShape, D, E>) => R; + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload, + TReturn extends unknown +> = ( + data: ResolveLoadedDataShape +) => TReturn; export type CreateUseLoaderArgs< - Q extends _Q, - D extends _D, - E extends _E, - R extends _R, - A = never + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload, + TReturn extends _TReturn, + TArg = never > = { /** Should return a list of RTK useQuery results. * Example: @@ -112,53 +140,84 @@ export type CreateUseLoaderArgs< * ``` */ useQueries: ( - ...args: OptionalGenericArg - ) => DataShape; + ...args: OptionalGenericArg + ) => DataShapeInput; /** Transforms the output of the queries */ transform?: ( - data: ResolveDataShape, D, E> - ) => R; + data: ResolveLoadedDataShape + ) => TReturn; }; -export type UseLoader = { - (...args: OptionalGenericArg): UseQueryResult; - original_args: CreateUseLoaderArgs; +export type UseLoader< + TArg, + TReturn, + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload +> = { + (...args: OptionalGenericArg): UseQueryResult; + original_args: CreateUseLoaderArgs< + TQueries, + TDeferred, + TPayload, + TReturn, + TArg + >; }; export type ComponentWithLoaderData< - P extends Record, - R extends unknown -> = (props: P, loaderData: R) => ReactElement; + TProps extends Record, + TReturn extends unknown +> = (props: TProps, loaderData: TReturn) => ReactElement; /** Use: `InferLoaderData`. Returns the return-value of the given loader's aggregated query. */ export type InferLoaderData = T extends Loader< - any, - infer X, - any, - any, - any, - any + any | never, + infer InferA, + any | never, + any | never, + any | never, + any | never > - ? X - : T extends Loader - ? Y - : T extends Loader - ? Z - : T extends Loader - ? W - : "could not parse"; + ? InferA + : T extends Loader + ? InferB + : T extends Loader + ? InferC + : T extends Loader + ? InferD + : T extends Loader< + any, + infer InferE, + never, + never, + never, + never + > + ? InferE + : T extends Loader + ? InferF + : T extends Loader + ? InferG + : T extends Loader + ? InferH + : T extends Loader + ? InferI + : T extends Loader + ? InferJ + : never; -export type Component

> = ( - props: P +export type Component> = ( + props: TProps ) => ReactElement; export type WhileFetchingArgs< - P extends unknown, - R extends unknown + TProps extends unknown, + TReturn extends unknown > = { /** Will be prepended before the component while the query is fetching */ - prepend?: (props: P, data?: R) => ReactElement; + prepend?: (props: TProps, data?: TReturn) => ReactElement; /** Will be appended after the component while the query is fetching */ - append?: (props: P, data?: R) => ReactElement; + append?: (props: TProps, data?: TReturn) => ReactElement; }; export type CustomLoaderProps = { @@ -184,91 +243,140 @@ export type CustomLoaderProps = { }; export type CreateLoaderArgs< - P extends unknown, - Q extends _Q, - D extends _D, - E extends _E, - R extends unknown = MakeDataRequired, - A = never -> = Partial> & { + TProps extends unknown, + TQueries extends _TQueries, + TDeferred extends _TDeferred, + TPayload extends _TPayload, + TReturn extends unknown, + TArg = never +> = Partial< + CreateUseLoaderArgs< + TQueries, + TDeferred, + TPayload, + TReturn, + TArg + > +> & { /** Generates an argument for the `queries` based on component props */ - queriesArg?: (props: P) => A; + queriesArg?: (props: TProps) => TArg; /** Determines what to render while loading (with no data to fallback on) */ - onLoading?: (props: P) => ReactElement; + onLoading?: (props: TProps) => ReactElement; /** Determines what to render when query fails. */ onError?: ( - props: P, + props: TProps, error: FetchBaseQueryError | SerializedError, joinedQuery: UseQueryResult ) => ReactElement; /** @deprecated Using onFetching might result in loss of internal state. Use `whileFetching` instead, or pass the query to the component */ onFetching?: ( - props: P, + props: TProps, renderBody: () => ReactElement ) => ReactElement; /** Determines what to render besides success-result while query is fetching. */ - whileFetching?: WhileFetchingArgs; + whileFetching?: WhileFetchingArgs; /** The component to use to switch between rendering the different query states. */ loaderComponent?: Component; }; export type CreateLoader< - P extends unknown, - Q extends _Q = never, - D extends _D = never, - E extends _E = never, - R extends unknown = MakeDataRequired, - A = never + TProps extends unknown, + TQueries extends _TQueries = never, + TDeferred extends _TDeferred = never, + TPayload extends _TPayload = never, + TReturn extends unknown = MakeDataRequired, + TArg = never > = ( - args: CreateLoaderArgs -) => Loader; + args: CreateLoaderArgs< + TProps, + TQueries, + TDeferred, + TPayload, + TReturn, + TArg + > +) => Loader< + TProps, + TReturn, + TQueries, + TDeferred, + TPayload, + TArg +>; export type Loader< - P extends unknown, - R extends unknown, - Q extends _Q, - D extends _D, - E extends _E, - A = never + TProps extends unknown, + TReturn extends unknown, + TQueries extends _TQueries = never, + TDeferred extends _TDeferred = never, + TPayload extends _TPayload = never, + TArg = never > = { /** A hook that runs all queries and returns aggregated result */ - useLoader: UseLoader; + useLoader: UseLoader< + TArg, + TReturn, + TQueries, + TDeferred, + TPayload + >; /** Generates an argument for the `queries` based on component props */ - queriesArg?: (props: P) => A; + queriesArg?: (props: TProps) => TArg; /** Determines what to render while loading (with no data to fallback on) */ - onLoading?: (props: P) => ReactElement; + onLoading?: (props: TProps) => ReactElement; /** Determines what to render when query fails. */ onError?: ( - props: P, + props: TProps, error: SerializedError | FetchBaseQueryError, joinedQuery: UseQueryResult ) => ReactElement; /** @deprecated Using onFetching might result in loss of internal state. Use `whileFetching` instead, or pass the query to the component */ onFetching?: ( - props: P, + props: TProps, renderBody: () => ReactElement ) => ReactElement; /** Determines what to render besides success-result while query is fetching. */ - whileFetching?: WhileFetchingArgs; + whileFetching?: WhileFetchingArgs; /** Returns a new `Loader` extended from this `Loader`, with given overrides. */ extend: < - Qb extends _Q = Q, - Db extends _D = D, - Eb extends _E = E, - Pb extends unknown = P, - Rb extends unknown = ResolveDataShape< - Qb, - Db, - Eb - > extends never - ? R extends never - ? Q - : R - : ResolveDataShape, Db, Eb>, - Ab = A + E_TQueries extends _TQueries = TQueries, + E_TDeferred extends _TDeferred = TDeferred, + E_TPayload extends _TPayload = TPayload, + E_TReturn extends unknown = AllEql< + TQueries, + E_TQueries, + TDeferred, + E_TDeferred, + TPayload, + E_TPayload + > extends true + ? TReturn + : ResolveLoadedDataShape< + E_TQueries, + E_TDeferred, + E_TPayload + >, + E_TProps extends unknown = TProps, + E_TArg = TArg >( - newLoader: Partial> - ) => Loader; + newLoader: Partial< + CreateLoaderArgs< + E_TProps, + E_TQueries, + E_TDeferred, + E_TPayload, + E_TReturn, + E_TArg + > + > + ) => Loader< + E_TProps, + E_TReturn, + E_TQueries, + E_TDeferred, + E_TPayload, + E_TArg + >; /** The component to use to switch between rendering the different query states. */ LoaderComponent: Component; }; @@ -296,11 +404,14 @@ export type CreateQueryReducerAction = }; }; +export type ConsumerProps> = + Record & T; + /************************************************/ /* Legacy/unused, for backwards compatibility */ /************************************************/ export type WithLoaderArgs< - P extends unknown, - R extends unknown, - A = never -> = Loader; + TProps extends unknown, + TReturn extends unknown, + TArg = never +> = Loader; diff --git a/src/withLoader.tsx b/src/withLoader.tsx index ba4281c..17e8cca 100644 --- a/src/withLoader.tsx +++ b/src/withLoader.tsx @@ -4,29 +4,45 @@ import React from "react"; import * as Types from "./types"; export const withLoader = < - P extends Record, - R extends unknown, - Q extends Types._Q, - D extends Types._D, - E extends Types._E, - A = never + TProps extends Record, + TReturn extends unknown, + TQueries extends Types._TQueries, + TDeferred extends Types._TDeferred, + TPayload extends Types._TPayload, + TArg = never >( - Component: Types.ComponentWithLoaderData, - loader: Types.Loader -): Types.Component

=> { - let CachedComponent: Types.ComponentWithLoaderData; - const LoadedComponent = (props: P) => { + Component: Types.ComponentWithLoaderData, + loader: Types.Loader< + TProps, + TReturn, + TQueries, + TDeferred, + TPayload, + TArg + > +): Types.Component => { + let CachedComponent: Types.ComponentWithLoaderData< + TProps, + TReturn + >; + const LoadedComponent = (props: TProps) => { const useLoaderArgs = []; if (loader.queriesArg) { useLoaderArgs.push(loader.queriesArg(props)); } const query = loader.useLoader( - ...(useLoaderArgs as Types.OptionalGenericArg) + ...(useLoaderArgs as Types.OptionalGenericArg) ); if (!CachedComponent) { CachedComponent = React.forwardRef( - Component as React.ForwardRefRenderFunction - ) as unknown as Types.ComponentWithLoaderData; + Component as React.ForwardRefRenderFunction< + TReturn, + TProps + > + ) as unknown as Types.ComponentWithLoaderData< + TProps, + TReturn + >; } const onLoading = loader.onLoading?.(props); @@ -42,7 +58,7 @@ export const withLoader = < } : undefined; - const onSuccess = (data: R) => ( + const onSuccess = (data: TReturn) => ( ); @@ -62,7 +78,7 @@ export const withLoader = < const onFetching = loader?.onFetching?.( props, query.data - ? () => onSuccess(query.data as R) + ? () => onSuccess(query.data as TReturn) : () => ); diff --git a/testing-app/src/tests.test.tsx b/testing-app/src/tests.test.tsx index 136fd0d..690824c 100644 --- a/testing-app/src/tests.test.tsx +++ b/testing-app/src/tests.test.tsx @@ -549,6 +549,7 @@ describe("withLoader", () => { return (

{loader.queries.pokemon.data.name}
+
{loader.foobar}