Begin work on data fetching

main
idylls 1 year ago
parent 4cd9a47dc3
commit 7fdd497980
Signed by: idylls
GPG Key ID: 52D7502B0C319049

@ -0,0 +1,4 @@
import { MakeClient } from "../common/deps/yaypi.ts";
import { Api } from "../common/api.ts";
export type Client = MakeClient<typeof Api>;

@ -1,7 +1,13 @@
import { h } from "preact";
import { Dispatch } from "../../utils.ts";
import { Action } from "./state.ts";
import { Client } from "../../fetch.ts";
import { assert } from "../../../common/deps/yaypi.ts";
import { Loader, useAsync } from "../../useAsync.ts";
import { Client } from "../../client.ts";
export const Schemas = (p: unknown) => {
return h("div", {}, "schemas loaded");
};
export const Home = (p: { dispatch: Dispatch<Action>; client: Client }) => {
const button = h(
@ -12,6 +18,12 @@ export const Home = (p: { dispatch: Dispatch<Action>; client: Client }) => {
"New schema",
);
const schemas = h(Loader, {
fn: async () => assert(await p.client.v1.listSchemas(null)),
args: [],
renderLoaded: Schemas,
});
const b = h(
"button",
{
@ -20,5 +32,5 @@ export const Home = (p: { dispatch: Dispatch<Action>; client: Client }) => {
"Say hello",
);
return h("div", {}, button, b);
return h("div", {}, schemas, button, b);
};

@ -1,37 +0,0 @@
import { Reducer, useMemo, useReducer } from "preact/hooks";
import { MakeClient, assert, makeClient } from "../common/deps/yaypi.ts";
import { Api } from "../common/api.ts";
export type Client = MakeClient<typeof Api>;
export type Loadable<T, E = string> =
| null
| { loading: true }
| { loading: boolean; data: T }
| { loading: boolean; error: string };
export const isLoading = <T>(loadable: Loadable<T>) => loadable?.loading;
export type Data = {
schemas: Loadable<ReturnType<Client["v1"]["listSchemas"]>>;
};
export type Action = never;
const reduce: Reducer<Data, Action> = (data, action) => {
return data;
};
const emptyData: Data = {
schemas: null,
};
// NOTE: this should only be used in App.ts
export const useFetch = () => {
const client = useMemo(() => makeClient(Api, "/api"), []);
const [data, reducer] = useReducer(reduce, emptyData);
const listSchemas = async (
...args: Parameters<typeof client.v1.listSchemas>
) => {
const schemas = assert(await client.v1.listSchemas(...args));
};
};

@ -0,0 +1,68 @@
import { Fragment, VNode, h } from "preact";
import { useState, useEffect } from "preact/hooks";
export type AsyncState<T, E = any> =
| { loading: true }
| { loading: boolean; data: T }
| { loading: boolean; error: E };
const asyncState = <T, E = any>(state: AsyncState<T, E>) => {
if ("data" in state) {
return state.loading ? ("stale" as const) : ("loaded" as const);
} else if ("error" in state) {
return state.loading ? ("staleError" as const) : ("error" as const);
}
return "loading";
};
export const useAsync = <Fn extends (...args: any) => Promise<any>, E = any>(
fn: Fn,
args: Parameters<Fn>,
): AsyncState<Awaited<ReturnType<Fn>>, E> => {
const [state, setState] = useState<AsyncState<Awaited<ReturnType<Fn>>>>({
loading: true,
});
useEffect(() => {
(async () => {
setState((state) => ({ ...state, loading: true }));
try {
const data = await fn(args);
setState((state) => ({ ...state, loading: false, data }));
} catch (error) {
setState((state) => ({ ...state, loading: false, error }));
}
})();
}, args);
return state;
};
export const Loader = <Fn extends (...args: any) => Promise<any>, E = any>(p: {
renderLoading?: () => VNode;
renderError?: (p: E, loading: boolean) => VNode;
renderLoaded: (p: Awaited<ReturnType<Fn>>, loading: boolean) => VNode;
fn: Fn;
args: Parameters<Fn>;
preferLoading?: boolean;
}) => {
const {
fn,
args,
preferLoading = true,
renderLoaded,
renderLoading = () => h(Fragment, {}, "Loading..."),
renderError = (e) => h(Fragment, {}, `${e}`),
} = p;
const res = useAsync(fn, args);
if (res.loading && preferLoading) {
return renderLoading();
} else if ("data" in res) {
return renderLoaded(res.data, res.loading);
} else if ("error" in res) {
return renderError(res.error, res.loading);
} else {
return renderLoading();
}
};
Loading…
Cancel
Save