import { useSelector, useDispatch } from 'react-redux'; import { useEffect, useMemo, useRef } from 'react'; import type { LoadingState } from 'robodux'; import type { QueryState } from './slice'; import { selectLoaderById, selectDataById } from './slice'; type ActionFn

= (p: P) => { toString: () => string }; type ActionFnSimple = () => { toString: () => string }; interface SagaAction

{ type: string; payload: { key: string; options: P }; } export interface UseApiProps

extends LoadingState { trigger: (p: P) => void; action: ActionFn

; } export interface UseApiSimpleProps extends LoadingState { trigger: () => void; action: ActionFn; } export interface UseApiAction extends LoadingState { trigger: () => void; action: A; } export type UseApiResult = | UseApiProps

| UseApiSimpleProps | UseApiAction; interface UseCacheResult extends UseApiAction { data: D | null; } /** * useLoader will take an action creator or action itself and return the associated * loader for it. * * @returns the loader object for an action creator or action * * @example * ```ts * import { useLoader } from 'saga-query/react'; * * import { api } from './api'; * * const fetchUsers = api.get('/users', function*() { * // ... * }); * * const View = () => { * const loader = useLoader(fetchUsers); * // or: const loader = useLoader(fetchUsers()); * return

{loader.isLoader ? 'Loading ...' : 'Done!'}
* } * ``` */ export function useLoader( action: SagaAction | ActionFn, ) { const id = typeof action === 'function' ? `${action}` : action.payload.key; return useSelector((s: S) => selectLoaderById(s, { id })); } /** * useApi will take an action creator or action itself and fetch * the associated loader and create a `trigger` function that you can call * later in your react component. * * This hook will *not* fetch the data for you because it does not know how to fetch * data from your redux state. * * @example * ```ts * import { useApi } from 'saga-query/react'; * * import { api } from './api'; * * const fetchUsers = api.get('/users', function*() { * // ... * }); * * const View = () => { * const { isLoading, trigger } = useApi(fetchUsers); * useEffect(() => { * trigger(); * }, []); * return
{isLoading ? : 'Loading' : 'Done!'}
* } * ``` */ export function useApi

>( action: A, ): UseApiAction; export function useApi

>( action: ActionFn

, ): UseApiProps

; export function useApi( action: ActionFnSimple, ): UseApiSimpleProps; export function useApi(action: any) { const dispatch = useDispatch(); const loader = useLoader(action); const trigger = (p: any) => { if (typeof action === 'function') { dispatch(action(p)); } else { dispatch(action); } }; const query = useMemo(() => { return { ...loader, trigger, action }; }, [loader]); return query; } /** * useQuery uses {@link useApi} and automatically calls `useApi().trigger()` * * @example * ```ts * import { useQuery } from 'saga-query/react'; * * import { api } from './api'; * * const fetchUsers = api.get('/users', function*() { * // ... * }); * * const View = () => { * const { isLoading } = useQuery(fetchUsers); * return

{isLoading ? : 'Loading' : 'Done!'}
* } * ``` */ export function useQuery

>( action: A, ): UseApiAction { const api = useApi(action); useEffect(() => { api.trigger(); }, [action.payload.key]); return api; } /** * useCache uses {@link useQuery} and automatically selects the cached data associated * with the action creator or action provided. * * @example * ```ts * import { useCache } from 'saga-query/react'; * * import { api } from './api'; * * const fetchUsers = api.get('/users', api.cache()); * * const View = () => { * const { isLoading, data } = useCache(fetchUsers()); * return

{isLoading ? : 'Loading' : data.length}
* } * ``` */ export function useCache( action: A, ): UseCacheResult { const id = action.payload.key; const data = useSelector((s: any) => selectDataById(s, { id })); const query = useQuery(action); const cache = useMemo(() => { return { ...query, data: data || null }; }, [query, data]); return cache; } /** * useLoaderSuccess will activate the callback provided when the loader transitions * from some state to success. * * @example * ```ts * import { useLoaderSuccess, useApi } from 'saga-query/react'; * * import { api } from './api'; * * const createUser = api.post('/users', function*(ctx, next) { * // ... * }); * * const View = () => { * const { loader, trigger } = useApi(createUser); * const onSubmit = () => { * trigger({ name: 'bob' }); * }; * * useLoaderSuccess(loader, () => { * // success! * // Use this callback to navigate to another view * }); * * return * } * ``` */ export function useLoaderSuccess( cur: Pick, success: () => any, ) { const prev = useRef(cur); useEffect(() => { if (prev.current.status !== 'success' && cur.status === 'success') { success(); } prev.current = cur; }, [cur.status]); }