Skip to content

Commit d347804

Browse files
POST, DELETE
1 parent 1a5375a commit d347804

3 files changed

Lines changed: 121 additions & 15 deletions

File tree

‎app/model/fetching.ts‎

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export const identifiers = {
22
getJSON: "getJSON",
3+
postJSON: "postJSON",
4+
deleteJSON: "deleteJSON",
35
} as const;
46

57
export function getJSON(url: string | URL) {
@@ -9,7 +11,27 @@ export function getJSON(url: string | URL) {
911
});
1012
}
1113

12-
export type FetchMessage = ReturnType<typeof getJSON>;
14+
export function postJSON(
15+
url: string | URL,
16+
body?: Parameters<typeof JSON.stringify>[0]
17+
) {
18+
return Object.freeze({
19+
type: identifiers.postJSON,
20+
url: url.toString(),
21+
body: body !== undefined ? JSON.stringify(body) : undefined,
22+
});
23+
}
24+
25+
export function deleteJSON(
26+
url: string | URL,
27+
) {
28+
return Object.freeze({
29+
type: identifiers.deleteJSON,
30+
url: url.toString(),
31+
});
32+
}
33+
34+
export type FetchMessage = ReturnType<typeof getJSON | typeof postJSON | typeof deleteJSON>;
1335
export type FetchReply = any;
1436

1537
export type FetchGenerator<Result> = Generator<
@@ -20,7 +42,7 @@ export type FetchGenerator<Result> = Generator<
2042

2143
export async function fetchComponent<Result>(
2244
generator: () => FetchGenerator<Result>,
23-
options: { baseURL?: URL, signal?: AbortSignal } = {}
45+
options: { baseURL?: URL; signal?: AbortSignal } = {}
2446
) {
2547
const gen = generator();
2648
let reply: FetchReply | undefined;
@@ -35,7 +57,24 @@ export async function fetchComponent<Result>(
3557

3658
if (result.value.type === identifiers.getJSON) {
3759
const url = new URL(result.value.url, options.baseURL);
38-
const data = fetch(url.toString(), { signal: options.signal }).then(res => res.json());
60+
const data = fetch(url.toString(), { signal: options.signal }).then(
61+
(res) => res.json()
62+
);
63+
reply = await data;
64+
} else if (result.value.type === identifiers.postJSON) {
65+
const url = new URL(result.value.url, options.baseURL);
66+
const data = fetch(url.toString(), {
67+
method: "POST",
68+
body: result.value.body,
69+
signal: options.signal,
70+
}).then((res) => res.json());
71+
reply = await data;
72+
} else if (result.value.type === identifiers.deleteJSON) {
73+
const url = new URL(result.value.url, options.baseURL);
74+
const data = fetch(url.toString(), {
75+
method: "DELETE",
76+
signal: options.signal,
77+
}).then((res) => res.json());
3978
reply = await data;
4079
}
4180
}

‎app/routes/make/fetch.tsx‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState } from "react";
2-
import { fetchComponent, getJSON } from "../../model/fetching";
2+
import { fetchComponent, getJSON, postJSON } from "../../model/fetching";
33
import { parseFormData, parseJSON, read } from "../../model/schemas";
4-
import { useAsyncFunc } from "../../view/async";
4+
import { useAsyncAction, useAsyncFunc } from "../../view/async";
55
import { CodeBlock } from "../../view/code";
66
import { NamedSection } from "../../view/semantics";
77

@@ -37,15 +37,49 @@ function* BundlePhobiaFormSchema() {
3737
};
3838
}
3939

40+
const publicStoreURL = new URL("https://public-store.collected.workers.dev");
41+
function* PublicStoreListItems() {
42+
const rawData: unknown = yield getJSON(new URL("/items", publicStoreURL));
43+
44+
return parseJSON(rawData, function* Schema() {
45+
const result: string[] = yield read.array("result", "string");
46+
return result;
47+
});
48+
}
49+
function* PublicStoreCreateItem() {
50+
const rawData: unknown = yield postJSON(new URL("/items", publicStoreURL));
51+
52+
return parseJSON(rawData, function* Schema() {
53+
const result: number = yield read.number("result");
54+
return result;
55+
});
56+
}
57+
function* PublicStoreDeleteItems() {
58+
yield postJSON(new URL("/items", publicStoreURL));
59+
}
60+
4061
export default function MakeFetchHappen() {
4162
const [packageName, updatePackageName] = useState("react");
4263

43-
const bundlephobiaData = useAsyncFunc(
64+
const [bundlephobiaData] = useAsyncFunc(
4465
(signal) =>
4566
fetchComponent(BundlePhobiaAPI.bind(null, packageName), { signal }),
4667
[packageName]
4768
);
4869

70+
const [performCreate, createResult] = useAsyncAction((signal) =>
71+
fetchComponent(PublicStoreCreateItem, { signal })
72+
);
73+
const [performDelete, deleteResult] = useAsyncAction((signal) =>
74+
fetchComponent(PublicStoreDeleteItems, { signal })
75+
);
76+
console.log(createResult);
77+
78+
const [listData] = useAsyncFunc(
79+
(signal) => fetchComponent(PublicStoreListItems, { signal }),
80+
[createResult.completedCount, deleteResult.completedCount]
81+
);
82+
4983
return (
5084
<main data-measure="center">
5185
<h1>Fetch</h1>
@@ -79,6 +113,22 @@ export default function MakeFetchHappen() {
79113
</CodeBlock>
80114
)}
81115
</NamedSection>
116+
117+
<NamedSection id="list" heading={<h2>List</h2>}>
118+
<button type="button" onClick={performCreate}>
119+
Add Random Item
120+
</button>
121+
<button type="button" onClick={performDelete}>
122+
Delete Items
123+
</button>
124+
{listData == null ? (
125+
<div>Loading…</div>
126+
) : (
127+
<CodeBlock language="json">
128+
{JSON.stringify(listData, null, 2)}
129+
</CodeBlock>
130+
)}
131+
</NamedSection>
82132
</main>
83133
);
84134
}

‎app/view/async.tsx‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useReducer, useState } from "react";
22

33
export function useAsyncFunc<Data>(
44
source: (signal: AbortSignal) => Promise<Data>,
5-
dependencies: Array<any>
6-
): null | Data {
7-
const [data, setData] = useState<Data | null>(null);
8-
5+
dependencies: Array<unknown>
6+
): readonly [null | Data, { requestCount: number, completedCount: number }] {
7+
const [state, setState] = useState<{ requestCount: number; completedCount: number; data: Data | null }>({
8+
requestCount: 0,
9+
completedCount: 0,
10+
data: null
11+
});
12+
913
useEffect(() => {
1014
const aborter = new AbortController();
11-
12-
setData(null);
13-
source(aborter.signal).then(setData);
15+
16+
setState(state => ({ ...state, requestCount: state.requestCount + 1 }));
17+
source(aborter.signal).then(data => {
18+
setState(state => ({ ...state, completedCount: state.completedCount + 1, data }))
19+
});
1420

1521
return () => aborter.abort();
1622
}, dependencies);
1723

18-
return data;
24+
return Object.freeze([state.data, state] as const);
25+
}
26+
27+
export function useAsyncAction<Data>(
28+
source: (signal: AbortSignal) => Promise<Data>
29+
): readonly [() => void, { data: null | Data, requestCount: number, completedCount: number }] {
30+
const [requestCount, request] = useReducer((n) => n + 1, 0);
31+
const [data, result] = useAsyncFunc(
32+
async (signal) => (requestCount > 0 ? source(signal) : null),
33+
[requestCount]
34+
);
35+
return Object.freeze([request, { data, requestCount, completedCount: result.completedCount }] as const);
1936
}

0 commit comments

Comments
 (0)