diff --git a/README.md b/README.md index 0dbe428..5d6af6e 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,17 @@ export const { } = hooks; ``` +If you'd like to define your Axios client in some other way (e.g. using React Context and/or hooks), you can specify a _function_ for getting the client. It is safe to use React hooks within this function -- it will be called according to the rules of hooks: + +```typescript +const hooks = createAPIHooks({ + name: 'my-api', + client: () => { + return useMyClient(); + }, +}); +``` + ### `useAPIQuery` Type-safe wrapper around `useQuery` from `react-query`. diff --git a/src/hooks.test.tsx b/src/hooks.test.tsx index d9baee3..87fa8b0 100644 --- a/src/hooks.test.tsx +++ b/src/hooks.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as TestingLibrary from '@testing-library/react'; import { useQuery as useReactQuery } from '@tanstack/react-query'; -import axios from 'axios'; +import axios, { AxiosInstance } from 'axios'; import { createAPIHooks } from './hooks'; import { createAPIMockingUtility } from './test-utils'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -1054,3 +1054,44 @@ describe('useAPICache', () => { }); }); }); + +test('passing a function for `client` is supported', async () => { + const TestContext = React.createContext(undefined); + + const { useAPIQuery } = createAPIHooks({ + name: 'test-name', + client: () => { + const client = React.useContext(TestContext); + if (!client) { + throw new Error('no client specified'); + } + return client; + }, + }); + + const TestComponent: React.FC = () => { + const query = useAPIQuery('GET /items', { filter: 'test-filter' }); + return <>{query.data?.message}; + }; + + network.mock('GET /items', { + status: 200, + data: { message: 'test-message-2' }, + }); + + const screen = TestingLibrary.render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); + + await TestingLibrary.waitFor(() => { + expect(screen.queryAllByText('test-message-2')).toBeDefined(); + }); +}); diff --git a/src/hooks.ts b/src/hooks.ts index 9133de9..584f0e4 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -14,16 +14,27 @@ import { APIClient, createQueryKey } from './util'; export type CreateAPIQueryHooksOptions = { name: string; - client: AxiosInstance; + /** + * An Axios client, or a function for retrieving one. This function can + * call React hooks -- it will be called according to the rules of hooks. + */ + client: AxiosInstance | (() => AxiosInstance); }; export const createAPIHooks = ({ name, client: axiosClient, }: CreateAPIQueryHooksOptions): APIQueryHooks => { - const client = new APIClient(axiosClient); + const useClient = () => + new APIClient( + // Since AxiosInstances themselves are functions, check for the `.get(...)` + // property to determine if this is a client, or a function to return a client. + 'get' in axiosClient ? axiosClient : axiosClient(), + ); + return { useAPIQuery: (route, payload, options) => { + const client = useClient(); const queryKey: QueryKey = [createQueryKey(name, route, payload)]; return useQuery( queryKey, @@ -35,6 +46,7 @@ export const createAPIHooks = ({ ); }, useInfiniteAPIQuery: (route, initPayload, options) => { + const client = useClient(); const queryKey: QueryKey = [ INFINITE_QUERY_KEY, createQueryKey(name, route, initPayload), @@ -58,12 +70,17 @@ export const createAPIHooks = ({ return query; }, - useAPIMutation: (route, options) => - useMutation( + useAPIMutation: (route, options) => { + const client = useClient(); + + return useMutation( (payload) => client.request(route, payload).then((res) => res.data), options, - ), + ); + }, useCombinedAPIQueries: (...routes) => { + const client = useClient(); + const queries = useQueries({ queries: routes.map(([endpoint, payload, options]) => ({ ...options,