@uktrade/react-component-library 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +2 -10
- package/dist/components/ApiQuery/ApiProvider.d.ts +10 -6
- package/dist/components/ApiQuery/ApiProvider.d.ts.map +1 -1
- package/dist/components/ApiQuery/ApiProvider.js +40 -20
- package/package.json +1 -1
- package/src/components/ApiQuery/ApiCreateClient.tsx +17 -0
- package/src/components/ApiQuery/ApiCreateHooks.tsx +76 -0
- package/src/components/ApiQuery/ApiProvider.tsx +105 -0
- package/src/components/ApiQuery/index.ts +3 -0
- package/src/index.ts +2 -1
package/Readme.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
A collection of reusable React components following GOV.UK design patterns.
|
|
4
4
|
|
|
5
|
-
---
|
|
6
|
-
|
|
7
5
|
## Table of Contents
|
|
8
6
|
|
|
9
7
|
* [Installation](#installation)
|
|
@@ -12,9 +10,7 @@ A collection of reusable React components following GOV.UK design patterns.
|
|
|
12
10
|
* [Build](#build)
|
|
13
11
|
* [Playground](#playground)
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Installation WIP
|
|
13
|
+
## Installation
|
|
18
14
|
|
|
19
15
|
```bash
|
|
20
16
|
npm install @uktrade/react-component-library
|
|
@@ -25,20 +21,16 @@ npm install @uktrade/react-component-library
|
|
|
25
21
|
* `react >= 19`
|
|
26
22
|
* `react-dom >= 19`
|
|
27
23
|
|
|
28
|
-
---
|
|
29
|
-
|
|
30
24
|
## Usage
|
|
31
25
|
|
|
32
26
|
Import components from the package:
|
|
33
27
|
|
|
34
28
|
```tsx
|
|
35
|
-
import { SummaryList, SummaryItem } from "react-component-library";
|
|
29
|
+
import { SummaryList, SummaryItem } from "@uktrade/react-component-library";
|
|
36
30
|
```
|
|
37
31
|
|
|
38
32
|
You can use them directly in your React applications.
|
|
39
33
|
|
|
40
|
-
---
|
|
41
|
-
|
|
42
34
|
## Development
|
|
43
35
|
|
|
44
36
|
### Build
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type QueryKey } from "@tanstack/react-query";
|
|
1
|
+
import { QueryClient, type QueryKey } from "@tanstack/react-query";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
+
export declare function getQueryClient(): QueryClient;
|
|
3
4
|
export declare function ApiProvider({ children }: {
|
|
4
5
|
children: ReactNode;
|
|
5
6
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -21,10 +22,13 @@ export declare function useApi<T>(key: QueryKey, queryFn: () => Promise<T>): {
|
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
24
|
export declare function useApiMutation<T>(mutationFn: () => Promise<T>): {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
mutation: import("@tanstack/react-query").UseMutationResult<T, Error, void, unknown>;
|
|
26
|
+
state: {
|
|
27
|
+
data: T | undefined;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
isError: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<T, Error, void, unknown>;
|
|
32
|
+
};
|
|
29
33
|
};
|
|
30
34
|
//# sourceMappingURL=ApiProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiProvider.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"ApiProvider.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EAIX,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASvC,wBAAgB,cAAc,gBAa7B;AAMD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAMhE;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAYF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;;;;;EAgBjE;AAYD,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;;;;;EAa7D"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { QueryClient, QueryClientProvider, useQuery, useMutation } from "@tanstack/react-query";
|
|
4
|
-
// To avoid cache duplication bugs we make sure to have
|
|
5
|
-
// per app.
|
|
4
|
+
// To avoid cache duplication bugs we make sure to have
|
|
5
|
+
// one single queryClient object per app.
|
|
6
6
|
let queryClient;
|
|
7
|
-
|
|
7
|
+
// Creates (once) and returns a shared QueryClient instance for the entire application.
|
|
8
|
+
// React Query relies on a single client to manage caching, retries, and background updates.
|
|
9
|
+
// This function ensures that only one instance is ever created.
|
|
10
|
+
export function getQueryClient() {
|
|
8
11
|
if (!queryClient) {
|
|
9
12
|
// Stores all query state, retries, and caching
|
|
10
13
|
queryClient = new QueryClient({
|
|
@@ -18,12 +21,23 @@ function getQueryClient() {
|
|
|
18
21
|
}
|
|
19
22
|
return queryClient;
|
|
20
23
|
}
|
|
21
|
-
//
|
|
24
|
+
// A React component that wraps the application with React Query’s QueryClientProvider.
|
|
25
|
+
// It injects the singleton QueryClient into React Query’s context so that all hooks (useQuery, useMutation, etc.)
|
|
26
|
+
// share the same cache and configuration.
|
|
27
|
+
// Every component that uses React Query must be rendered inside this provider.
|
|
22
28
|
export function ApiProvider({ children }) {
|
|
23
|
-
return (
|
|
24
|
-
// Injects the client into React Query’s context
|
|
25
|
-
_jsx(QueryClientProvider, { client: getQueryClient(), children: children }));
|
|
29
|
+
return (_jsx(QueryClientProvider, { client: getQueryClient(), children: children }));
|
|
26
30
|
}
|
|
31
|
+
/*
|
|
32
|
+
A generic wrapper around React Query’s useQuery. It is not tied to GET requests, it is used for any API operation that should
|
|
33
|
+
- run automatically on mount
|
|
34
|
+
- cache results
|
|
35
|
+
- support refetching
|
|
36
|
+
|
|
37
|
+
It accepts a `key` (unique identifier for the query) and a `queryFn` (function that fetches the data). It returns:
|
|
38
|
+
- `query` — the full React Query query object
|
|
39
|
+
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
40
|
+
*/
|
|
27
41
|
export function useApi(key, queryFn) {
|
|
28
42
|
// From React Query queryKey and queryFn.
|
|
29
43
|
// `queryKey` is a unique identifier for this query (React Query uses it for caching and refetching).
|
|
@@ -40,20 +54,26 @@ export function useApi(key, queryFn) {
|
|
|
40
54
|
},
|
|
41
55
|
};
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
/*
|
|
58
|
+
A wrapper around React Query’s `useMutation` hook for POST/PUT/PATCH/DELETE operations. It is used for:
|
|
59
|
+
- operations that do not run automatically on mount
|
|
60
|
+
- operations that do not cache results
|
|
61
|
+
|
|
62
|
+
It accepts `mutationFn` as function that performs the mutation request, and It returns:
|
|
63
|
+
|
|
64
|
+
- `mutation` — the full React Query mutation object
|
|
65
|
+
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
66
|
+
*/
|
|
50
67
|
export function useApiMutation(mutationFn) {
|
|
51
|
-
const
|
|
68
|
+
const mutation = useMutation({ mutationFn });
|
|
52
69
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
mutation, // Full React Query object
|
|
71
|
+
state: {
|
|
72
|
+
data: mutation.data,
|
|
73
|
+
isLoading: mutation.isPending,
|
|
74
|
+
isError: mutation.isError,
|
|
75
|
+
error: mutation.error,
|
|
76
|
+
mutate: mutation.mutate,
|
|
77
|
+
},
|
|
58
78
|
};
|
|
59
79
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import type { ClientOptions } from "openapi-fetch";
|
|
3
|
+
|
|
4
|
+
// `extends {}` required to fix type error (Type 'Paths' does not satisfy the constraint '{}'.)
|
|
5
|
+
export function createApiClient<Paths extends {}>(
|
|
6
|
+
options?: ClientOptions
|
|
7
|
+
) {
|
|
8
|
+
const defaultOptions: Partial<ClientOptions> = {
|
|
9
|
+
baseUrl: "/api",
|
|
10
|
+
credentials: "include",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Merge/Override user options with the defaults
|
|
14
|
+
const finalOptions = { ...defaultOptions, ...options };
|
|
15
|
+
|
|
16
|
+
return createClient<Paths>(finalOptions);
|
|
17
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type { PathsWithMethod } from "openapi-typescript-helpers";
|
|
4
|
+
|
|
5
|
+
import { createApiClient } from "./ApiCreateClient";
|
|
6
|
+
import { useApi } from "./ApiProvider";
|
|
7
|
+
|
|
8
|
+
export function createApiHooks<Paths extends {}>(
|
|
9
|
+
client: ReturnType<typeof createApiClient<Paths>>
|
|
10
|
+
) {
|
|
11
|
+
// type GetParams<P extends PathsWithMethod<Paths, "get">> =
|
|
12
|
+
// Parameters<typeof client.GET<P>>[1];
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
get: <P extends PathsWithMethod<Paths, "get">>(
|
|
16
|
+
path: P,
|
|
17
|
+
params?: any // GetParams<P> More work to be done to fight openapi-fetch’s internal generics!!!!
|
|
18
|
+
) => {
|
|
19
|
+
return useApi(
|
|
20
|
+
["get", path, JSON.stringify(params ?? {})],
|
|
21
|
+
async () => {
|
|
22
|
+
const res = await client.GET(path, params);
|
|
23
|
+
if (res.error) throw res.error;
|
|
24
|
+
return res.data;
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
post: <P extends PathsWithMethod<Paths, "post">>(
|
|
30
|
+
path: P,
|
|
31
|
+
params?: any // GetParams<P> More work to be done to fight openapi-fetch’s internal generics!!!!
|
|
32
|
+
) => {
|
|
33
|
+
return useApi(
|
|
34
|
+
["post", path, JSON.stringify(params ?? {})],
|
|
35
|
+
async () => {
|
|
36
|
+
const res = await client.POST(path, params);
|
|
37
|
+
if (res.error) throw res.error;
|
|
38
|
+
return res.data;
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// TODO: implement other methods (put, delete), furthermore generalize the mutation method
|
|
44
|
+
// for all methods (No GET) to avoid React Query’s useQuery from running automatically on mount
|
|
45
|
+
|
|
46
|
+
// Same here (params?: any)
|
|
47
|
+
// postMutation: <P extends PathsWithMethod<Paths, "post">>(path: P, params?: any) => {
|
|
48
|
+
// return useApiMutation(async () => {
|
|
49
|
+
// const res = await client.POST(path, params);
|
|
50
|
+
// if (res.error) throw res.error;
|
|
51
|
+
// return res.data;
|
|
52
|
+
// });
|
|
53
|
+
// },
|
|
54
|
+
|
|
55
|
+
// mutation: <
|
|
56
|
+
// M extends "post" | "put" | "patch" | "delete",
|
|
57
|
+
// P extends PathsWithMethod<Paths, M>
|
|
58
|
+
// >(
|
|
59
|
+
// method: M,
|
|
60
|
+
// path: P,
|
|
61
|
+
// params?: any
|
|
62
|
+
// ) => {
|
|
63
|
+
// return useApiMutation(async () => {
|
|
64
|
+
// const res = await client[method.toUpperCase() as "POST" | "PUT" | "PATCH" | "DELETE"](path, params);
|
|
65
|
+
// if (res.error) throw res.error;
|
|
66
|
+
// return res.data;
|
|
67
|
+
// });
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
// ToDo: Generalize this:
|
|
71
|
+
// const res = await client.POST(path, params);
|
|
72
|
+
// if (res.error) throw res.error;
|
|
73
|
+
// return res.data;
|
|
74
|
+
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
QueryClient,
|
|
5
|
+
QueryClientProvider,
|
|
6
|
+
useQuery,
|
|
7
|
+
useMutation,
|
|
8
|
+
type QueryKey
|
|
9
|
+
} from "@tanstack/react-query";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
|
|
12
|
+
// To avoid cache duplication bugs we make sure to have
|
|
13
|
+
// one single queryClient object per app.
|
|
14
|
+
let queryClient: QueryClient | undefined;
|
|
15
|
+
|
|
16
|
+
// Creates (once) and returns a shared QueryClient instance for the entire application.
|
|
17
|
+
// React Query relies on a single client to manage caching, retries, and background updates.
|
|
18
|
+
// This function ensures that only one instance is ever created.
|
|
19
|
+
export function getQueryClient() {
|
|
20
|
+
if (!queryClient) {
|
|
21
|
+
// Stores all query state, retries, and caching
|
|
22
|
+
queryClient = new QueryClient({
|
|
23
|
+
defaultOptions: {
|
|
24
|
+
queries: {
|
|
25
|
+
retry: false,
|
|
26
|
+
refetchOnWindowFocus: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return queryClient;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A React component that wraps the application with React Query’s QueryClientProvider.
|
|
35
|
+
// It injects the singleton QueryClient into React Query’s context so that all hooks (useQuery, useMutation, etc.)
|
|
36
|
+
// share the same cache and configuration.
|
|
37
|
+
// Every component that uses React Query must be rendered inside this provider.
|
|
38
|
+
export function ApiProvider({ children }: { children: ReactNode }) {
|
|
39
|
+
return (
|
|
40
|
+
<QueryClientProvider client={getQueryClient()}>
|
|
41
|
+
{children}
|
|
42
|
+
</QueryClientProvider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ApiQueryState<T> = {
|
|
47
|
+
data?: T;
|
|
48
|
+
isLoading: boolean;
|
|
49
|
+
isError: boolean;
|
|
50
|
+
error?: unknown;
|
|
51
|
+
refetch: () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
A generic wrapper around React Query’s useQuery. It is not tied to GET requests, it is used for any API operation that should
|
|
56
|
+
- run automatically on mount
|
|
57
|
+
- cache results
|
|
58
|
+
- support refetching
|
|
59
|
+
|
|
60
|
+
It accepts a `key` (unique identifier for the query) and a `queryFn` (function that fetches the data). It returns:
|
|
61
|
+
- `query` — the full React Query query object
|
|
62
|
+
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
63
|
+
*/
|
|
64
|
+
export function useApi<T>(key: QueryKey, queryFn: () => Promise<T>) {
|
|
65
|
+
// From React Query queryKey and queryFn.
|
|
66
|
+
// `queryKey` is a unique identifier for this query (React Query uses it for caching and refetching).
|
|
67
|
+
// `queryFn` is the function that actually fetches the data, API call by using openapi-fetch module.
|
|
68
|
+
const query = useQuery({ queryKey: key, queryFn });
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
query, // raw data to make sure you do not block full React Query object from propagating
|
|
72
|
+
state: {
|
|
73
|
+
data: query.data, // fetched data
|
|
74
|
+
isLoading: query.isFetching, // boolean for current fetch in progress
|
|
75
|
+
isError: query.isError, // boolean for error state
|
|
76
|
+
error: query.error, // error object if it failed
|
|
77
|
+
refetch: query.refetch, // function to manually re-run the query
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
A wrapper around React Query’s `useMutation` hook for POST/PUT/PATCH/DELETE operations. It is used for:
|
|
84
|
+
- operations that do not run automatically on mount
|
|
85
|
+
- operations that do not cache results
|
|
86
|
+
|
|
87
|
+
It accepts `mutationFn` as function that performs the mutation request, and It returns:
|
|
88
|
+
|
|
89
|
+
- `mutation` — the full React Query mutation object
|
|
90
|
+
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
91
|
+
*/
|
|
92
|
+
export function useApiMutation<T>(mutationFn: () => Promise<T>) {
|
|
93
|
+
const mutation = useMutation({ mutationFn });
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
mutation, // Full React Query object
|
|
97
|
+
state: {
|
|
98
|
+
data: mutation.data,
|
|
99
|
+
isLoading: mutation.isPending,
|
|
100
|
+
isError: mutation.isError,
|
|
101
|
+
error: mutation.error,
|
|
102
|
+
mutate: mutation.mutate,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,4 +9,5 @@ export * from "./components/Button";
|
|
|
9
9
|
export * from "./components/ButtonGroup";
|
|
10
10
|
export * from "./components/BackLink";
|
|
11
11
|
export * from "./components/LoadingSpinner/LoadingSpinner";
|
|
12
|
-
export * from "./components/ApiBoundary/ApiBoundary";
|
|
12
|
+
export * from "./components/ApiBoundary/ApiBoundary";
|
|
13
|
+
export * from "./components/ApiQuery";
|