import type { Api, BaseQueryFn, EndpointDefinitions, InfiniteQueryDefinition, Module, MutationDefinition, PrefetchOptions, QueryArgFrom, QueryDefinition, QueryKeys, } from '@reduxjs/toolkit/query'; import { batch as rrBatch, useDispatch as rrUseDispatch, useSelector as rrUseSelector, useStore as rrUseStore, } from '@octanejs/redux'; import type { CreateSelectorFunction } from 'reselect'; import { createSelector as _createSelector } from 'reselect'; import type { Dispatch, Store, UnknownAction } from 'redux'; import type { InfiniteQueryHooks, MutationHooks, QueryHooks } from './buildHooks'; import { buildHooks } from './buildHooks'; import type { HooksWithUniqueNames } from './namedHooks'; // Module-local type for the bundler's process.env.NODE_ENV replacement. declare const process: { env: { NODE_ENV?: string } }; const isQueryDefinition = (definition: { type?: string }) => definition.type === 'query'; const isMutationDefinition = (definition: { type?: string }) => definition.type === 'mutation'; const isInfiniteQueryDefinition = (definition: { type?: string }) => definition.type === 'infinitequery'; const safeAssign = Object.assign; const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1); const countObjectKeys = (value: object) => Object.keys(value).length; export const reactHooksModuleName = /* @__PURE__ */ Symbol(); export type ReactHooksModule = typeof reactHooksModuleName; declare module '@reduxjs/toolkit/query' { export interface ApiModules< // eslint-disable-next-line @typescript-eslint/no-unused-vars BaseQuery extends BaseQueryFn, Definitions extends EndpointDefinitions, // eslint-disable-next-line @typescript-eslint/no-unused-vars ReducerPath extends string, // eslint-disable-next-line @typescript-eslint/no-unused-vars TagTypes extends string, > { [reactHooksModuleName]: { /** * Endpoints based on the input endpoints provided to `createApi`, containing `select`, `hooks` and `action matchers`. */ endpoints: { [K in keyof Definitions]: Definitions[K] extends QueryDefinition ? QueryHooks : Definitions[K] extends MutationDefinition ? MutationHooks : Definitions[K] extends InfiniteQueryDefinition ? InfiniteQueryHooks : never; }; /** * A hook that accepts a string endpoint name, and provides a callback that when called, pre-fetches the data for that endpoint. */ usePrefetch>( endpointName: EndpointName, options?: PrefetchOptions, ): (arg: QueryArgFrom, options?: PrefetchOptions) => void; } & HooksWithUniqueNames; } } type UseDispatch = () => Dispatch; type UseSelector = ( selector: (state: TState) => Selected, equalityFnOrOptions?: ((left: Selected, right: Selected) => boolean) | object, ) => Selected; type UseStore = () => Store; export interface ReactHooksModuleOptions { /** * The hooks from `@octanejs/redux` to be used */ hooks?: { /** * The version of the `useDispatch` hook to be used */ useDispatch: UseDispatch; /** * The version of the `useSelector` hook to be used */ useSelector: UseSelector; /** * The version of the `useStore` hook to be used */ useStore: UseStore; }; /** * The version of the `batchedUpdates` function to be used */ batch?: (callback: () => void) => void; /** * Enables performing asynchronous tasks immediately within a render. * * @example * * ```ts * import { * buildCreateApi, * coreModule, * reactHooksModule * } from '@octanejs/redux-toolkit/query/react' * * const createApi = buildCreateApi( * coreModule(), * reactHooksModule({ unstable__sideEffectsInRender: true }) * ) * ``` */ unstable__sideEffectsInRender?: boolean; /** * A selector creator (usually from `reselect`, or matching the same signature) */ createSelector?: CreateSelectorFunction; } /** * Creates a module that generates Octane hooks from endpoints, for use with `buildCreateApi`. * * @example * ```ts * const MyContext = createContext(null); * const customCreateApi = buildCreateApi( * coreModule(), * reactHooksModule({ * hooks: { * useDispatch: createDispatchHook(MyContext), * useSelector: createSelectorHook(MyContext), * useStore: createStoreHook(MyContext) * } * }) * ); * ``` * * @returns A module for use with `buildCreateApi` */ export const reactHooksModule = ({ batch = rrBatch, hooks = { useDispatch: rrUseDispatch as UseDispatch, useSelector: rrUseSelector as UseSelector, useStore: rrUseStore as UseStore, }, createSelector = _createSelector, unstable__sideEffectsInRender = false, ...rest }: ReactHooksModuleOptions = {}): Module => { if (process.env.NODE_ENV !== 'production') { const hookNames = ['useDispatch', 'useSelector', 'useStore'] as const; let warned = false; for (const hookName of hookNames) { // warn for old hook options if (countObjectKeys(rest) > 0) { if ((rest as Partial)[hookName]) { if (!warned) { console.warn( 'As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:' + '\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`', ); warned = true; } } // migrate // @ts-ignore hooks[hookName] = rest[hookName]; } // then make sure we have them all if (typeof hooks[hookName] !== 'function') { throw new Error( `When using custom hooks for context, all ${ hookNames.length } hooks need to be provided: ${hookNames.join( ', ', )}.\nHook ${hookName} was either not provided or not a function.`, ); } } } return { name: reactHooksModuleName, init(api, { serializeQueryArgs }, context) { const anyApi = api as any as Api, any, any, ReactHooksModule>; const { buildQueryHooks, buildInfiniteQueryHooks, buildMutationHook, usePrefetch } = buildHooks({ api, moduleOptions: { batch, hooks, unstable__sideEffectsInRender, createSelector, }, serializeQueryArgs, context, }); safeAssign(anyApi, { usePrefetch }); safeAssign(context, { batch }); return { injectEndpoint(endpointName, definition) { if (isQueryDefinition(definition)) { const { useQuery, useLazyQuery, useLazyQuerySubscription, useQueryState, useQuerySubscription, } = buildQueryHooks(endpointName); safeAssign(anyApi.endpoints[endpointName], { useQuery, useLazyQuery, useLazyQuerySubscription, useQueryState, useQuerySubscription, }); (api as any)[`use${capitalize(endpointName)}Query`] = useQuery; (api as any)[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery; } if (isMutationDefinition(definition)) { const useMutation = buildMutationHook(endpointName); safeAssign(anyApi.endpoints[endpointName], { useMutation, }); (api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation; } else if (isInfiniteQueryDefinition(definition)) { const { useInfiniteQuery, useInfiniteQuerySubscription, useInfiniteQueryState } = buildInfiniteQueryHooks(endpointName); safeAssign(anyApi.endpoints[endpointName], { useInfiniteQuery, useInfiniteQuerySubscription, useInfiniteQueryState, }); (api as any)[`use${capitalize(endpointName)}InfiniteQuery`] = useInfiniteQuery; } }, }; }, }; };