@jobber/hooks 2.0.3-dar.45 → 2.0.3-dar.47
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/package.json +4 -3
- package/src/index.ts +10 -0
- package/src/useAssert/index.ts +1 -0
- package/src/useAssert/useAssert.stories.mdx +32 -0
- package/src/useAssert/useAssert.tsx +19 -0
- package/src/useCollectionQuery/index.ts +1 -0
- package/src/useCollectionQuery/mdxUtils.ts +190 -0
- package/src/useCollectionQuery/test-utilities/index.ts +3 -0
- package/src/useCollectionQuery/test-utilities/mocks.tsx +147 -0
- package/src/useCollectionQuery/test-utilities/queries.ts +95 -0
- package/src/useCollectionQuery/test-utilities/utils.ts +3 -0
- package/src/useCollectionQuery/uniqueEdges.tsx +26 -0
- package/src/useCollectionQuery/uniqueNodes.tsx +12 -0
- package/src/useCollectionQuery/useCollectionQuery.stories.mdx +129 -0
- package/src/useCollectionQuery/useCollectionQuery.test.tsx +419 -0
- package/src/useCollectionQuery/useCollectionQuery.ts +359 -0
- package/src/useFocusTrap/index.ts +1 -0
- package/src/useFocusTrap/useFocusTrap.stories.mdx +49 -0
- package/src/useFocusTrap/useFocusTrap.test.tsx +66 -0
- package/src/useFocusTrap/useFocusTrap.ts +64 -0
- package/src/useFormState/index.ts +1 -0
- package/src/useFormState/useFormState.stories.mdx +70 -0
- package/src/useFormState/useFormState.ts +10 -0
- package/src/useIsMounted/index.ts +1 -0
- package/src/useIsMounted/useIsMounted.stories.mdx +59 -0
- package/src/useIsMounted/useIsMounted.test.tsx +18 -0
- package/src/useIsMounted/useIsMounted.ts +30 -0
- package/src/useLiveAnnounce/index.ts +1 -0
- package/src/useLiveAnnounce/useLiveAnnounce.stories.mdx +38 -0
- package/src/useLiveAnnounce/useLiveAnnounce.test.tsx +55 -0
- package/src/useLiveAnnounce/useLiveAnnounce.tsx +47 -0
- package/src/useOnKeyDown/index.ts +1 -0
- package/src/useOnKeyDown/useOnKeyDown.stories.mdx +67 -0
- package/src/useOnKeyDown/useOnKeyDown.test.tsx +31 -0
- package/src/useOnKeyDown/useOnKeyDown.ts +52 -0
- package/src/usePasswordStrength/index.ts +1 -0
- package/src/usePasswordStrength/usePasswordStrength.stories.mdx +51 -0
- package/src/usePasswordStrength/usePasswordStrength.ts +21 -0
- package/src/useRefocusOnActivator/index.ts +1 -0
- package/src/useRefocusOnActivator/useRefocusOnActivator.stories.mdx +39 -0
- package/src/useRefocusOnActivator/useRefocusOnActivator.ts +26 -0
- package/src/useResizeObserver/index.ts +1 -0
- package/src/useResizeObserver/useResizeObserver.stories.mdx +134 -0
- package/src/useResizeObserver/useResizeObserver.ts +78 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/hooks",
|
|
3
|
-
"version": "2.0.3-dar.
|
|
3
|
+
"version": "2.0.3-dar.47+2396d450",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.js",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"bootstrap": "npm run clean; npm run build"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"dist/*"
|
|
13
|
+
"dist/*",
|
|
14
|
+
"src"
|
|
14
15
|
],
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"@apollo/react-testing": "^4.0.0",
|
|
@@ -38,5 +39,5 @@
|
|
|
38
39
|
"@apollo/client": "^3.7.10",
|
|
39
40
|
"react": "^18"
|
|
40
41
|
},
|
|
41
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "2396d45004d1464b14b68e1861dd9835ae7c3d98"
|
|
42
43
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./useAssert";
|
|
2
|
+
export * from "./useCollectionQuery";
|
|
3
|
+
export * from "./useFocusTrap";
|
|
4
|
+
export * from "./useFormState";
|
|
5
|
+
export * from "./useIsMounted";
|
|
6
|
+
export * from "./useLiveAnnounce";
|
|
7
|
+
export * from "./useOnKeyDown";
|
|
8
|
+
export * from "./usePasswordStrength";
|
|
9
|
+
export * from "./useRefocusOnActivator";
|
|
10
|
+
export * from "./useResizeObserver";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useAssert } from "./useAssert";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta title="Hooks/useAssert" />
|
|
4
|
+
|
|
5
|
+
# useAssert
|
|
6
|
+
|
|
7
|
+
During development, disrupt the render of a component when a conditional is met.
|
|
8
|
+
This is helpful when you want to limit something that Typescript can't do...
|
|
9
|
+
yet.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { useAssert } from "@jobber/hooks";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const child: ReactElement = <Avatar initials="JBR" />;
|
|
17
|
+
// Make sure the child can only be an <Icon /> component
|
|
18
|
+
useAssert(child.type !== Icon, "You can only pass in an <Icon /> child");
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Warn
|
|
22
|
+
|
|
23
|
+
If you still want to assert but doesn't want to disrupt the render, you can pass
|
|
24
|
+
in a `warn: true` param. This will throw a warning on your browser console.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const url: string = "http://getjobber.com";
|
|
28
|
+
// Make sure the url always starts with https://
|
|
29
|
+
useAssert(url.startsWith("http://"), "Please pass in an `https://` url", {
|
|
30
|
+
warn: true,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import process from "process";
|
|
2
|
+
|
|
3
|
+
interface Options {
|
|
4
|
+
readonly warn: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function useAssert(
|
|
8
|
+
shouldShow: boolean,
|
|
9
|
+
message: string,
|
|
10
|
+
options?: Options,
|
|
11
|
+
) {
|
|
12
|
+
if (process.env.NODE_ENV !== "production" && shouldShow) {
|
|
13
|
+
if (options?.warn) {
|
|
14
|
+
console.warn(message);
|
|
15
|
+
} else {
|
|
16
|
+
throw new Error(message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useCollectionQuery } from "./useCollectionQuery";
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ApolloClient, InMemoryCache, gql } from "@apollo/client";
|
|
2
|
+
|
|
3
|
+
export interface ListQueryType {
|
|
4
|
+
allPlanets: {
|
|
5
|
+
__typename?: "PlanetsConnection";
|
|
6
|
+
pageInfo: {
|
|
7
|
+
hasNextPage: boolean;
|
|
8
|
+
endCursor: string;
|
|
9
|
+
};
|
|
10
|
+
edges: Array<{
|
|
11
|
+
__typename?: "PlanetsEdge";
|
|
12
|
+
node: {
|
|
13
|
+
__typename?: "Planet";
|
|
14
|
+
name: "string";
|
|
15
|
+
id: "string";
|
|
16
|
+
};
|
|
17
|
+
cursor: string;
|
|
18
|
+
}>;
|
|
19
|
+
totalCount?: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const LIST_QUERY = gql`
|
|
24
|
+
query ListQuery($cursor: String) {
|
|
25
|
+
allPlanets(first: 4, after: $cursor) {
|
|
26
|
+
pageInfo {
|
|
27
|
+
hasNextPage
|
|
28
|
+
endCursor
|
|
29
|
+
}
|
|
30
|
+
edges {
|
|
31
|
+
node {
|
|
32
|
+
name
|
|
33
|
+
id
|
|
34
|
+
}
|
|
35
|
+
cursor
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export const apolloClient = new ApolloClient({
|
|
42
|
+
uri: "https://swapi-graphql.netlify.app/.netlify/functions/index",
|
|
43
|
+
cache: new InMemoryCache(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
interface LoadingState {
|
|
47
|
+
loadingStatus: string;
|
|
48
|
+
loading: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getLoadingState(
|
|
52
|
+
loadingInitialContent: boolean,
|
|
53
|
+
loadingRefresh: boolean,
|
|
54
|
+
loadingNextPage: boolean,
|
|
55
|
+
): LoadingState {
|
|
56
|
+
if (loadingInitialContent) {
|
|
57
|
+
return {
|
|
58
|
+
loading: true,
|
|
59
|
+
loadingStatus: "Initial Loading",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (loadingRefresh) {
|
|
63
|
+
return {
|
|
64
|
+
loading: true,
|
|
65
|
+
loadingStatus: "Refreshing",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (loadingNextPage) {
|
|
69
|
+
return {
|
|
70
|
+
loading: true,
|
|
71
|
+
loadingStatus: "Fetching More",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
loading: false,
|
|
76
|
+
loadingStatus: "Loaded",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const propsList = [
|
|
81
|
+
{
|
|
82
|
+
id: 0,
|
|
83
|
+
title: "query",
|
|
84
|
+
caption: "The graphQL query that fetches the collection",
|
|
85
|
+
value: "DocumentNode",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 1,
|
|
89
|
+
title: "queryOptions",
|
|
90
|
+
caption:
|
|
91
|
+
"A list of options for us to pass into the apollo `useQuery` hook. \
|
|
92
|
+
Click to see more query options!",
|
|
93
|
+
url: "https://www.apollographql.com/docs/react/data/queries/#options",
|
|
94
|
+
value: "QueryHookOptions",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 2,
|
|
98
|
+
title: "getCollectionByPath",
|
|
99
|
+
caption:
|
|
100
|
+
"A function that returns the location where the \
|
|
101
|
+
{@link Collection} is located. The collection is the part of the \
|
|
102
|
+
result that needs to be paginated.",
|
|
103
|
+
value: "GetCollectionByPathFunction<TQuery>",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 3,
|
|
107
|
+
title: "subscription (optional)",
|
|
108
|
+
caption:
|
|
109
|
+
"A list of subscription options if \
|
|
110
|
+
you want to create a GraphQL subscription to listen for more content.",
|
|
111
|
+
value: "SubscriptionProps",
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
export const subscriptionPropsList = [
|
|
116
|
+
{
|
|
117
|
+
id: 0,
|
|
118
|
+
title: "document",
|
|
119
|
+
caption:
|
|
120
|
+
"The graphQL subscription that listens for more data. This query \
|
|
121
|
+
should return a single Node that matches the data structure in \
|
|
122
|
+
getCollectionByPath<TQuery>(...).edges.node and \
|
|
123
|
+
getCollectionByPath<TQuery>(...).nodes",
|
|
124
|
+
value: "DocumentNode",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 1,
|
|
128
|
+
title: "options",
|
|
129
|
+
caption:
|
|
130
|
+
" A list of variables to pass into the apollo `subscribeToMore` function.",
|
|
131
|
+
value: "SubscribeToMoreOptions<TSubscription>",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 2,
|
|
135
|
+
title: "getNodeByPath",
|
|
136
|
+
caption:
|
|
137
|
+
" A function that returns the location where the `Node` is \
|
|
138
|
+
located on the TSubscription object. It should return a single Node \
|
|
139
|
+
that matches the data structure in \
|
|
140
|
+
getCollectionByPath<TQuery>(...).edges.node and \
|
|
141
|
+
getCollectionByPath<TQuery>(...).nodes",
|
|
142
|
+
value: "GetNodeByPath<TSubscription>",
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
export const returnValues = [
|
|
147
|
+
{
|
|
148
|
+
id: 0,
|
|
149
|
+
title: "data",
|
|
150
|
+
caption: "The payload returned from the query",
|
|
151
|
+
value: "ListQueryType | undefined",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 1,
|
|
155
|
+
title: "error",
|
|
156
|
+
caption: "Any errors returned from the query",
|
|
157
|
+
value: "ApolloError | undefined",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 2,
|
|
161
|
+
title: "refresh",
|
|
162
|
+
caption: "A funtion that enables you to re-execute the query",
|
|
163
|
+
value: "() => void",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 3,
|
|
167
|
+
title: "nextPage",
|
|
168
|
+
caption:
|
|
169
|
+
"A funtion that helps you fetch the next set of results for a paginated list",
|
|
170
|
+
value: "() => void",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 4,
|
|
174
|
+
title: "loadingRefresh",
|
|
175
|
+
caption: "An indicator that a refresh is in progress",
|
|
176
|
+
value: "boolean",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 5,
|
|
180
|
+
title: "loadingNextPage",
|
|
181
|
+
caption: "An indicator that a fetch more is in progress",
|
|
182
|
+
value: "boolean",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 6,
|
|
186
|
+
title: "loadingInitialContent",
|
|
187
|
+
caption: "An indicator that the initial content is being fetched",
|
|
188
|
+
value: "boolean",
|
|
189
|
+
},
|
|
190
|
+
];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { DocumentNode } from "@apollo/client";
|
|
2
|
+
import { MockedProvider, MockedResponse } from "@apollo/react-testing";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { v1 as uuidv1 } from "uuid";
|
|
5
|
+
import { SUBSCRIPTION_QUERY } from "./queries";
|
|
6
|
+
|
|
7
|
+
export function wrapper(mocks: MockedResponse[]) {
|
|
8
|
+
function ApolloMockedProvider({
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
children: React.ReactElement;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<MockedProvider addTypename={true} mocks={mocks}>
|
|
15
|
+
{children}
|
|
16
|
+
</MockedProvider>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return ApolloMockedProvider as React.FunctionComponent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let listQueryHasNextPage = true;
|
|
24
|
+
export const listQueryResponseMock = jest.fn(id => {
|
|
25
|
+
return {
|
|
26
|
+
data: {
|
|
27
|
+
conversation: {
|
|
28
|
+
__typename: "Conversation",
|
|
29
|
+
smsMessages: {
|
|
30
|
+
__typename: "SMSMessageConnection",
|
|
31
|
+
edges: [
|
|
32
|
+
{
|
|
33
|
+
__typename: "SMSMessageEdge",
|
|
34
|
+
node: {
|
|
35
|
+
__typename: "SMSMessage",
|
|
36
|
+
id: id || uuidv1(),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
nodes: [
|
|
41
|
+
{
|
|
42
|
+
__typename: "SMSMessage",
|
|
43
|
+
id: id || uuidv1(),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
pageInfo: {
|
|
47
|
+
__typename: "PageInfo",
|
|
48
|
+
endCursor: "MZ",
|
|
49
|
+
hasNextPage: listQueryHasNextPage,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const listQueryWithTotalCountResponseMock = jest.fn(id => {
|
|
58
|
+
return {
|
|
59
|
+
data: {
|
|
60
|
+
conversation: {
|
|
61
|
+
__typename: "Conversation",
|
|
62
|
+
smsMessages: {
|
|
63
|
+
__typename: "SMSMessageConnection",
|
|
64
|
+
edges: [
|
|
65
|
+
{
|
|
66
|
+
__typename: "SMSMessageEdge",
|
|
67
|
+
node: {
|
|
68
|
+
__typename: "SMSMessage",
|
|
69
|
+
id: id || uuidv1(),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
nodes: [
|
|
74
|
+
{
|
|
75
|
+
__typename: "SMSMessage",
|
|
76
|
+
id: id || uuidv1(),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
pageInfo: {
|
|
80
|
+
__typename: "PageInfo",
|
|
81
|
+
endCursor: "MZ",
|
|
82
|
+
hasNextPage: listQueryHasNextPage,
|
|
83
|
+
},
|
|
84
|
+
totalCount: 42,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const subscriptionQueryMock = jest.fn(id => {
|
|
92
|
+
return {
|
|
93
|
+
data: {
|
|
94
|
+
conversationMessage: {
|
|
95
|
+
__typename: "conversationMessage",
|
|
96
|
+
id: "other stuff",
|
|
97
|
+
smsMessage: {
|
|
98
|
+
__typename: "SMSMessageData",
|
|
99
|
+
id: id,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export function buildListRequestMock(
|
|
107
|
+
query: DocumentNode,
|
|
108
|
+
responseMock: jest.Mock,
|
|
109
|
+
id?: string | undefined,
|
|
110
|
+
searchTerm?: string | undefined,
|
|
111
|
+
) {
|
|
112
|
+
return {
|
|
113
|
+
request: {
|
|
114
|
+
query: query,
|
|
115
|
+
variables: { searchTerm: searchTerm },
|
|
116
|
+
},
|
|
117
|
+
result: () => responseMock(id),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildSubscriptionRequestMock(id?: string | undefined) {
|
|
122
|
+
return {
|
|
123
|
+
request: {
|
|
124
|
+
query: SUBSCRIPTION_QUERY,
|
|
125
|
+
},
|
|
126
|
+
result: () => subscriptionQueryMock(id),
|
|
127
|
+
delay: 100,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function buildListRequestMockForNextPage(
|
|
132
|
+
query: DocumentNode,
|
|
133
|
+
responseMock: jest.Mock,
|
|
134
|
+
id?: string | undefined,
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
137
|
+
request: {
|
|
138
|
+
query: query,
|
|
139
|
+
variables: { cursor: "MZ" },
|
|
140
|
+
},
|
|
141
|
+
result: () => responseMock(id),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function setListQueryMockHasNextPage(hasNextPage: boolean) {
|
|
146
|
+
listQueryHasNextPage = hasNextPage;
|
|
147
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { gql } from "@apollo/client";
|
|
2
|
+
|
|
3
|
+
export const LIST_QUERY = gql`
|
|
4
|
+
query ConversationMessages($cursor: string, $searchTerm: string) {
|
|
5
|
+
conversation(id: "MQ==") {
|
|
6
|
+
smsMessages(first: 1, after: $cursor, searchTerm: $searchTerm) {
|
|
7
|
+
edges {
|
|
8
|
+
node {
|
|
9
|
+
__typename
|
|
10
|
+
id
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
nodes {
|
|
14
|
+
__typename
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
pageInfo {
|
|
18
|
+
endCursor
|
|
19
|
+
hasNextPage
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export const LIST_QUERY_WITH_TOTAL_COUNT = gql`
|
|
27
|
+
query ConversationMessages($cursor: string, $searchTerm: string) {
|
|
28
|
+
conversation(id: "MQ==") {
|
|
29
|
+
smsMessages(first: 1, after: $cursor, searchTerm: $searchTerm) {
|
|
30
|
+
edges {
|
|
31
|
+
node {
|
|
32
|
+
__typename
|
|
33
|
+
id
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
nodes {
|
|
37
|
+
__typename
|
|
38
|
+
id
|
|
39
|
+
}
|
|
40
|
+
pageInfo {
|
|
41
|
+
endCursor
|
|
42
|
+
hasNextPage
|
|
43
|
+
}
|
|
44
|
+
totalCount
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
export interface ListQueryType {
|
|
51
|
+
__typename?: "Query";
|
|
52
|
+
conversation?: {
|
|
53
|
+
__typename?: "Conversation";
|
|
54
|
+
smsMessages: {
|
|
55
|
+
edges: Array<{
|
|
56
|
+
__typename?: "SMSMessageConnection";
|
|
57
|
+
node: {
|
|
58
|
+
__typename?: "SMSMessage";
|
|
59
|
+
id: string;
|
|
60
|
+
};
|
|
61
|
+
cursor: string;
|
|
62
|
+
}>;
|
|
63
|
+
nodes: Array<{
|
|
64
|
+
__typename?: "SMSMessage";
|
|
65
|
+
id: string;
|
|
66
|
+
}>;
|
|
67
|
+
pageInfo: {
|
|
68
|
+
endCursor: string;
|
|
69
|
+
hasNextPage: boolean;
|
|
70
|
+
};
|
|
71
|
+
totalCount?: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const SUBSCRIPTION_QUERY = gql`
|
|
77
|
+
subscription ConversationMessage($conversationId: EncodedId!) {
|
|
78
|
+
conversationMessage(conversationId: $conversationId) {
|
|
79
|
+
smsMessage {
|
|
80
|
+
__typename
|
|
81
|
+
id
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
export interface SubscriptionQueryType {
|
|
88
|
+
__typename?: "Subscription";
|
|
89
|
+
conversationMessage?: {
|
|
90
|
+
smsMessage: {
|
|
91
|
+
__typename?: "SMSMessage";
|
|
92
|
+
id: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Node } from "./uniqueNodes";
|
|
2
|
+
|
|
3
|
+
export interface Edge {
|
|
4
|
+
__typename?: unknown;
|
|
5
|
+
node: Node;
|
|
6
|
+
cursor: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createEdge(node: Node): Edge {
|
|
10
|
+
return {
|
|
11
|
+
node: node,
|
|
12
|
+
cursor: "",
|
|
13
|
+
__typename: `${node.__typename}Edge`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function uniqueEdges(edges: Edge[]): Edge[] {
|
|
18
|
+
const result = new Map<string, Edge>();
|
|
19
|
+
edges.forEach(edge => {
|
|
20
|
+
result.set(
|
|
21
|
+
`${edge.__typename}-${edge.node.__typename}-${edge.node.id}`,
|
|
22
|
+
edge,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
return Array.from(result.values());
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Node {
|
|
2
|
+
id: unknown;
|
|
3
|
+
__typename?: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function uniqueNodes(nodes: Node[]): Node[] {
|
|
7
|
+
const result = new Map<string, Node>();
|
|
8
|
+
nodes.forEach(node => {
|
|
9
|
+
result.set(`${node.__typename}-${node.id}`, node);
|
|
10
|
+
});
|
|
11
|
+
return Array.from(result.values());
|
|
12
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Canvas, Meta, Story } from "@storybook/addon-docs";
|
|
2
|
+
import { List } from "@jobber/components/List";
|
|
3
|
+
import { Button } from "@jobber/components/Button";
|
|
4
|
+
import { Spinner } from "@jobber/components/Spinner";
|
|
5
|
+
import { InlineLabel } from "@jobber/components/InlineLabel";
|
|
6
|
+
import * as hooks from ".";
|
|
7
|
+
import {
|
|
8
|
+
LIST_QUERY,
|
|
9
|
+
apolloClient,
|
|
10
|
+
getLoadingState,
|
|
11
|
+
propsList,
|
|
12
|
+
returnValues,
|
|
13
|
+
subscriptionPropsList,
|
|
14
|
+
} from "./mdxUtils";
|
|
15
|
+
|
|
16
|
+
<Meta title="Hooks/useCollectionQuery" component={hooks.useCollectionQuery} />
|
|
17
|
+
|
|
18
|
+
# UseCollectionQuery
|
|
19
|
+
|
|
20
|
+
`useCollectionQuery` is a generalized hook that aids in the interaction of data
|
|
21
|
+
collections. For use with any lists populated by graphQL collections.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useCollectionQuery } from "@jobber/hooks";
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
data,
|
|
28
|
+
error,
|
|
29
|
+
refresh,
|
|
30
|
+
nextPage,
|
|
31
|
+
loadingRefresh,
|
|
32
|
+
loadingNextPage,
|
|
33
|
+
loadingInitialContent,
|
|
34
|
+
} = useCollectionQuery<ListQueryType, SubscriptionType>({
|
|
35
|
+
query: LIST_QUERY,
|
|
36
|
+
queryOptions: {
|
|
37
|
+
fetchPolicy: "network-only",
|
|
38
|
+
nextFetchPolicy: "cache-first",
|
|
39
|
+
},
|
|
40
|
+
getCollectionByPath(items) {
|
|
41
|
+
return items.path.to.collection;
|
|
42
|
+
},
|
|
43
|
+
subscription: {
|
|
44
|
+
// subscriptions are optional
|
|
45
|
+
document: SUBSCRIPTION_QUERY,
|
|
46
|
+
getNodeByPath(items) {
|
|
47
|
+
return items.path.to.node;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
<Canvas>
|
|
53
|
+
<Story name="useCollectionQuery">
|
|
54
|
+
{() => {
|
|
55
|
+
const {
|
|
56
|
+
data,
|
|
57
|
+
refresh,
|
|
58
|
+
nextPage,
|
|
59
|
+
loadingRefresh,
|
|
60
|
+
loadingNextPage,
|
|
61
|
+
loadingInitialContent,
|
|
62
|
+
} = hooks.useCollectionQuery({
|
|
63
|
+
// useCollectionQuery should be called with the query type, and
|
|
64
|
+
// optionally, the subscription type. The Canvas errors with
|
|
65
|
+
// typing included, so typing has been removed in this example.
|
|
66
|
+
// Please see the first example for appropriate typing.
|
|
67
|
+
query: LIST_QUERY,
|
|
68
|
+
queryOptions: {
|
|
69
|
+
fetchPolicy: "network-only",
|
|
70
|
+
nextFetchPolicy: "cache-first",
|
|
71
|
+
client: apolloClient,
|
|
72
|
+
},
|
|
73
|
+
getCollectionByPath(items) {
|
|
74
|
+
return items.allPlanets;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const { loadingStatus, loading } = getLoadingState(
|
|
78
|
+
loadingInitialContent,
|
|
79
|
+
loadingRefresh,
|
|
80
|
+
loadingNextPage
|
|
81
|
+
);
|
|
82
|
+
let items = [];
|
|
83
|
+
if (data) {
|
|
84
|
+
items = data.allPlanets.edges.map(edge => {
|
|
85
|
+
return {
|
|
86
|
+
section: "Star Wars Planets",
|
|
87
|
+
id: edge.node.id,
|
|
88
|
+
icon: "starFill",
|
|
89
|
+
iconColor: "green",
|
|
90
|
+
content: edge.node.name,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<InlineLabel size="large">{loadingStatus}</InlineLabel>
|
|
97
|
+
{loading && <Spinner size="small" inline={true} />}
|
|
98
|
+
<List items={items} />
|
|
99
|
+
<Button
|
|
100
|
+
label={"Refresh"}
|
|
101
|
+
onClick={() => {
|
|
102
|
+
refresh();
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<Button
|
|
106
|
+
label={"Fetch More"}
|
|
107
|
+
onClick={() => {
|
|
108
|
+
nextPage();
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}}
|
|
114
|
+
</Story>
|
|
115
|
+
</Canvas>
|
|
116
|
+
|
|
117
|
+
## Props
|
|
118
|
+
|
|
119
|
+
<List items={propsList} />
|
|
120
|
+
|
|
121
|
+
## Subscription props
|
|
122
|
+
|
|
123
|
+
<List items={subscriptionPropsList} />
|
|
124
|
+
|
|
125
|
+
## Return values
|
|
126
|
+
|
|
127
|
+
<List items={returnValues} />
|
|
128
|
+
|
|
129
|
+
---
|