Skip to content

Commit

Permalink
feat: allow specifying a client using a function
Browse files Browse the repository at this point in the history
  • Loading branch information
swain committed Aug 20, 2023
1 parent ec0f592 commit ef8aac9
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 6 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<APIEndpoints>({
name: 'my-api',
client: () => {
return useMyClient();
},
});
```
### `useAPIQuery`
Type-safe wrapper around `useQuery` from `react-query`.
Expand Down
43 changes: 42 additions & 1 deletion src/hooks.test.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -1054,3 +1054,44 @@ describe('useAPICache', () => {
});
});
});

test('passing a function for `client` is supported', async () => {
const TestContext = React.createContext<AxiosInstance | undefined>(undefined);

const { useAPIQuery } = createAPIHooks<TestEndpoints>({
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(<TestComponent />, {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
<TestContext.Provider
value={axios.create({ baseURL: 'https://www.lifeomic.com' })}
>
{children}
</TestContext.Provider>
</QueryClientProvider>
),
});

await TestingLibrary.waitFor(() => {
expect(screen.queryAllByText('test-message-2')).toBeDefined();
});
});
27 changes: 22 additions & 5 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <Endpoints extends RoughEndpoints>({
name,
client: axiosClient,
}: CreateAPIQueryHooksOptions): APIQueryHooks<Endpoints> => {
const client = new APIClient<Endpoints>(axiosClient);
const useClient = () =>
new APIClient<Endpoints>(
// 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,
Expand All @@ -35,6 +46,7 @@ export const createAPIHooks = <Endpoints extends RoughEndpoints>({
);
},
useInfiniteAPIQuery: (route, initPayload, options) => {
const client = useClient();
const queryKey: QueryKey = [
INFINITE_QUERY_KEY,
createQueryKey(name, route, initPayload),
Expand All @@ -58,12 +70,17 @@ export const createAPIHooks = <Endpoints extends RoughEndpoints>({

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,
Expand Down

0 comments on commit ef8aac9

Please sign in to comment.