Skip to content

Commit 74f1149

Browse files
Add fetch page
1 parent 7cf8160 commit 74f1149

11 files changed

Lines changed: 257 additions & 106 deletions

File tree

‎app/model/fetching.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ export type FetchGenerator<Result> = Generator<
1818
FetchReply | undefined
1919
>;
2020

21-
export async function fetchController<Result>(
22-
baseURL: URL,
23-
generator: () => FetchGenerator<Result>
21+
export async function fetchJSONComponent<Result>(
22+
generator: () => FetchGenerator<Result>,
23+
options: { baseURL?: URL } = {}
2424
) {
2525
const gen = generator();
2626
let reply: FetchReply | undefined;
@@ -34,7 +34,7 @@ export async function fetchController<Result>(
3434
reply = undefined;
3535

3636
if (result.value.type === identifiers.getJSON) {
37-
const url = new URL(result.value.url, baseURL);
37+
const url = new URL(result.value.url, options.baseURL);
3838
const data = fetch(url.toString()).then(res => res.json());
3939
reply = await data;
4040
}

‎app/model/schemas.ts‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const types = {
22
string: "string",
33
number: "number",
4+
boolean: "boolean",
45
array: "array",
56
} as const;
67

@@ -18,7 +19,17 @@ function readNumber(name: string) {
1819
};
1920
}
2021

21-
function readArray(name: string, itemType: typeof types.string | typeof types.number) {
22+
function readBoolean(name: string) {
23+
return {
24+
type: types.boolean,
25+
name,
26+
};
27+
}
28+
29+
function readArray(
30+
name: string,
31+
itemType: typeof types.string | typeof types.number
32+
) {
2233
return {
2334
type: types.array,
2435
name,
@@ -29,16 +40,17 @@ function readArray(name: string, itemType: typeof types.string | typeof types.nu
2940
export const read = {
3041
string: readString,
3142
number: readNumber,
43+
boolean: readBoolean,
3244
array: readArray,
3345
};
3446

3547
export type SchemaMessagePrimitive = ReturnType<
36-
typeof readString | typeof readNumber
37-
>;
38-
export type SchemaMessage = ReturnType<
39-
typeof readString | typeof readNumber | typeof readArray
48+
typeof readString | typeof readNumber | typeof readBoolean
4049
>;
41-
export type SchemaReply = string | number;
50+
export type SchemaMessage =
51+
| SchemaMessagePrimitive
52+
| ReturnType<typeof readArray>;
53+
export type SchemaReply = string | number | boolean;
4254
// export type SchemaGenerator<Result> = Generator<ReturnType<typeof read.string>, Result, string> | Generator<ReturnType<typeof read.number>, Result, number>;
4355
export type SchemaGenerator<Result> = Generator<
4456
SchemaMessage,
@@ -70,10 +82,16 @@ export function parseJSON<Result>(
7082
if (typeof (source as any)[result.value.name] === "number") {
7183
reply = (source as any)[result.value.name];
7284
}
85+
} else if (result.value.type === types.boolean) {
86+
if (typeof (source as any)[result.value.name] === "boolean") {
87+
reply = (source as any)[result.value.name];
88+
}
7389
} else if (result.value.type === types.array) {
7490
const itemType = result.value.itemType;
7591
if (Array.isArray((source as any)[result.value.name])) {
76-
const sanitizedItems = (source as any)[result.value.name].map((x: any) => typeof x === itemType ? x : null);
92+
const sanitizedItems = (source as any)[result.value.name].map(
93+
(x: any) => (typeof x === itemType ? x : null)
94+
);
7795
reply = sanitizedItems;
7896
}
7997
}
@@ -105,5 +123,6 @@ export function parseFormData<Result>(
105123
reply = parseFloat(source.get(result.value.name) as string);
106124
}
107125
}
126+
// TODO: handle boolean
108127
}
109128
}

‎app/navs/primary.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export const makeNavItems = (
2626
<li>
2727
<NavLink href="/make/sqlite">{"SQLite"}</NavLink>
2828
</li>
29+
<li>
30+
<NavLink href="/make/fetch">{"Fetch"}</NavLink>
31+
</li>
2932
<li>
3033
<NavLink href="/make/cli">{"CLI"}</NavLink>
3134
</li>

‎app/root.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ function Document({ children }: { children: React.ReactNode }) {
5353
if (nextItem != null) {
5454
nextItem.querySelector("a")?.click();
5555
}
56+
} else if (e.key === "z") {
57+
document.documentElement.classList.toggle('zoomed');
5658
}
5759
}
5860

‎app/routes/make/fetch.tsx‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useState } from "react";
2+
import { render } from "react-dom";
3+
import { fetchJSONComponent, getJSON } from "../../model/fetching";
4+
import { parseFormData, parseJSON, read } from "../../model/schemas";
5+
import { useAsyncFunc } from "../../view/async";
6+
import { CodeBlock } from "../../view/code";
7+
import { NamedSection } from "../../view/semantics";
8+
9+
function* BundlePhobiaAPI(packageName: string) {
10+
const url = new URL("https://bundlephobia.com/api/size");
11+
url.searchParams.set("package", packageName);
12+
const rawData: unknown = yield getJSON(url);
13+
14+
const parsedData = parseJSON(rawData, function* Schema() {
15+
const name: string = yield read.string("name");
16+
const description: string = yield read.string("description");
17+
const size: number = yield read.number("size");
18+
const sizeGzipped: number = yield read.number("gzip");
19+
const hasSideEffects: boolean = yield read.boolean("hasSideEffects");
20+
21+
return {
22+
name,
23+
description,
24+
size,
25+
sizeGzipped,
26+
hasSideEffects,
27+
};
28+
});
29+
30+
return parsedData;
31+
}
32+
33+
function* BundlePhobiaFormSchema() {
34+
const packageName: string = yield read.string("packageName");
35+
36+
return {
37+
packageName,
38+
};
39+
}
40+
41+
export default function MakeFetchHappen() {
42+
const [packageName, updatePackageName] = useState("react");
43+
44+
const bundlephobiaData = useAsyncFunc(
45+
() =>
46+
fetchJSONComponent(
47+
BundlePhobiaAPI.bind(null, packageName),
48+
// function* () {
49+
// return yield* BundlePhobiaAPI(packageName);
50+
// },
51+
{ baseURL: new URL("https://swapi.py4e.com/") }
52+
),
53+
[packageName]
54+
);
55+
56+
return (
57+
<main data-measure="center">
58+
<h1>Fetch</h1>
59+
60+
<NamedSection id="bundlephobia" heading={<h2>Bundlephobia</h2>}>
61+
<form
62+
autoComplete="off"
63+
autoCorrect="off"
64+
onSubmit={(event) => {
65+
event.preventDefault();
66+
const data = parseFormData(
67+
new FormData(event.currentTarget),
68+
BundlePhobiaFormSchema
69+
);
70+
updatePackageName(data.packageName);
71+
}}
72+
>
73+
<label>
74+
<div>Package name:</div>
75+
<input type="text" name="packageName" defaultValue={packageName} />
76+
</label>
77+
</form>
78+
79+
<h3>{packageName}</h3>
80+
81+
{bundlephobiaData == null ? (
82+
<div>Loading…</div>
83+
) : (
84+
<CodeBlock language="json">
85+
{JSON.stringify(bundlephobiaData, null, 2)}
86+
</CodeBlock>
87+
)}
88+
</NamedSection>
89+
</main>
90+
);
91+
}

‎app/routes/make/schema.tsx‎

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {
1818
} from "../../model/schemas";
1919
import { formatJavaScript } from "../../view/codeFormatting";
2020
import { Await, FinalResult } from "../../types/helpers";
21-
import { fetchController, FetchGenerator, getJSON } from "../../model/fetching";
22-
import { useMemo } from "react";
21+
import { fetchJSONComponent, getJSON } from "../../model/fetching";
22+
import { useAsyncFunc } from "../../view/async";
2323

2424
export let meta: MetaFunction = () => {
2525
return {
@@ -66,24 +66,6 @@ function useFetch<Data>(source: URL): null | Data {
6666
return data;
6767
}
6868

69-
function usePromise<Data>(source: Promise<Data>): null | Data {
70-
const [data, setData] = useState<Data | null>(null);
71-
72-
useEffect(() => {
73-
source.then(setData);
74-
}, [source]);
75-
76-
return data;
77-
}
78-
79-
function useAsyncFunc<Data>(
80-
source: () => Promise<Data>,
81-
dependencies: Array<any>
82-
): null | Data {
83-
const promise = useMemo(source, dependencies);
84-
return usePromise(promise);
85-
}
86-
8769
interface AWSRegion {
8870
primary: string;
8971
secondary: string;
@@ -131,12 +113,12 @@ function* SwapiPersonSchema(): SchemaGenerator<SwapiPersonData> {
131113
name,
132114
height,
133115
mass,
134-
films
116+
films,
135117
};
136118
}
137119
function* SwapiPersonResource() {
138-
const rawData: unknown = yield getJSON('/api/people/1');
139-
120+
const rawData: unknown = yield getJSON("/api/people/1");
121+
140122
const parsedData = parseJSON(rawData, function* Schema() {
141123
const name: string = yield read.string("name");
142124
const height: string = yield read.string("height");
@@ -147,15 +129,15 @@ function* SwapiPersonResource() {
147129
name,
148130
height,
149131
mass,
150-
films
132+
films,
151133
};
152134
});
153135

154136
return parsedData;
155137
}
156-
type SwapiPersonData2 = FinalResult<ReturnType<typeof SwapiPersonResource>>
138+
type SwapiPersonData2 = FinalResult<ReturnType<typeof SwapiPersonResource>>;
157139

158-
export default function MakeRenderer() {
140+
export default function MakeSchema() {
159141
const data: Await<ReturnType<typeof loader>> = useRouteData();
160142

161143
function renderExample(input: any): JSX.Element {
@@ -183,12 +165,13 @@ export default function MakeRenderer() {
183165
const personData =
184166
personDataRaw != null ? parseJSON(personDataRaw, SwapiPersonSchema) : null;
185167

186-
const personData2 = useAsyncFunc(() =>
187-
fetchController(
188-
new URL("https://swapi.py4e.com/"),
189-
SwapiPersonResource
190-
),
191-
[]);
168+
const personData2 = useAsyncFunc(
169+
() =>
170+
fetchJSONComponent(SwapiPersonResource, {
171+
baseURL: new URL("https://swapi.py4e.com/"),
172+
}),
173+
[]
174+
);
192175

193176
return (
194177
<main data-measure="center">

0 commit comments

Comments
 (0)