- commit
- 98a9de7
- parent
- bcdad66
- author
- Eric Bower
- date
- 2024-01-19 18:04:55 +0000 UTC
chore: folder/file org cleanup (#33)
16 files changed,
+311,
-412
+1,
-2
1@@ -227,12 +227,11 @@ import type {
2 ApiCtx,
3 CreateAction,
4 CreateActionWithPayload,
5- Next,
6 FetchJson,
7 MiddlewareApiCo,
8 Supervisor,
9 } from "./types.ts";
10-import type { Payload } from "../types.ts";
11+import type { Next, Payload } from "../types.ts";
12 import type { Operation } from "../deps.ts";
13
14 export type ApiName = string | string[];
+1,
-1
1@@ -1,5 +1,5 @@
2-import type { Next } from "./query/mod.ts";
3 import { Instruction, Operation } from "./deps.ts";
4+import type { Next } from "./types.ts";
5
6 export interface BaseCtx {
7 // deno-lint-ignore no-explicit-any
+1,
-2
1@@ -9,10 +9,9 @@ import type {
2 CreateActionWithPayload,
3 FetchJson,
4 MiddlewareApiCo,
5- Next,
6 Supervisor,
7 } from "./types.ts";
8-import type { Payload } from "../types.ts";
9+import type { Next, Payload } from "../types.ts";
10 import type { Operation } from "../deps.ts";
11
12 export type ApiName = string | string[];
+2,
-1
1@@ -1,5 +1,6 @@
2 // deno-lint-ignore-file no-explicit-any
3-import type { ApiCtx, ApiRequest, Next } from "./types.ts";
4+import type { ApiCtx, ApiRequest } from "./types.ts";
5+import type { Next } from "../types.ts";
6 import { createThunks } from "./thunk.ts";
7 import type { ThunksApi } from "./thunk.ts";
8 import type { ApiName, QueryApi } from "./api-types.ts";
+2,
-1
1@@ -1,7 +1,8 @@
2 import { sleep } from "../deps.ts";
3 import { safe } from "../fx/mod.ts";
4-import type { FetchCtx, FetchJsonCtx, Next } from "./types.ts";
5+import type { FetchCtx, FetchJsonCtx } from "./types.ts";
6 import { isObject, noop } from "./util.ts";
7+import type { Next } from "../types.ts";
8
9 /**
10 * This middleware converts the name provided to {@link createApi}
+1,
-1
1@@ -5,11 +5,11 @@ import type {
2 ApiRequest,
3 FetchJsonCtx,
4 MiddlewareApi,
5- Next,
6 PerfCtx,
7 RequiredApiRequest,
8 ThunkCtx,
9 } from "./types.ts";
10+import type { Next } from "../types.ts";
11 import { mergeRequest } from "./util.ts";
12 import * as fetchMdw from "./fetch.ts";
13 import { log } from "../log.ts";
+1,
-2
1@@ -1,5 +1,5 @@
2 import { compose } from "../compose.ts";
3-import type { ActionWithPayload, Payload } from "../types.ts";
4+import type { ActionWithPayload, Next, Payload } from "../types.ts";
5 import { keepAlive } from "../mod.ts";
6 import { takeEvery } from "../action.ts";
7 import { isFn, isObject } from "./util.ts";
8@@ -10,7 +10,6 @@ import type {
9 CreateActionWithPayload,
10 Middleware,
11 MiddlewareCo,
12- Next,
13 Supervisor,
14 ThunkCtx,
15 } from "./types.ts";
+1,
-2
1@@ -4,6 +4,7 @@ import type {
2 ActionWithPayload,
3 LoaderItemState,
4 LoaderPayload,
5+ Next,
6 Payload,
7 } from "../types.ts";
8
9@@ -97,8 +98,6 @@ export type MiddlewareApiCo<Ctx extends ApiCtx = ApiCtx> =
10 | Middleware<Ctx>
11 | Middleware<Ctx>[];
12
13-export type Next = () => Operation<void>;
14-
15 export interface CreateActionPayload<P = any, ApiSuccess = any> {
16 name: string;
17 key: string;
M
react.ts
+275,
-1
1@@ -1,3 +1,277 @@
2-export * from "./store/react.ts";
3+import {
4+ Provider as ReduxProvider,
5+ React,
6+ useDispatch,
7+ useSelector,
8+} from "./deps.ts";
9+import type { AnyState, LoaderState } from "./types.ts";
10+import type { ThunkAction } from "./query/mod.ts";
11+import { type FxSchema, type FxStore, PERSIST_LOADER_ID } from "./store/mod.ts";
12+
13 export { useDispatch, useSelector } from "./deps.ts";
14 export type { TypedUseSelectorHook } from "./deps.ts";
15+
16+const {
17+ useContext,
18+ useEffect,
19+ useRef,
20+ createContext,
21+ createElement: h,
22+} = React;
23+
24+type ActionFn<P = any> = (p: P) => { toString: () => string };
25+type ActionFnSimple = () => { toString: () => string };
26+
27+export interface UseApiProps<P = any> extends LoaderState {
28+ trigger: (p: P) => void;
29+ action: ActionFn<P>;
30+}
31+export interface UseApiSimpleProps extends LoaderState {
32+ trigger: () => void;
33+ action: ActionFn;
34+}
35+export interface UseApiAction<A extends ThunkAction = ThunkAction>
36+ extends LoaderState {
37+ trigger: () => void;
38+ action: A;
39+}
40+export type UseApiResult<P, A extends ThunkAction = ThunkAction> =
41+ | UseApiProps<P>
42+ | UseApiSimpleProps
43+ | UseApiAction<A>;
44+
45+export interface UseCacheResult<D = any, A extends ThunkAction = ThunkAction>
46+ extends UseApiAction<A> {
47+ data: D | null;
48+}
49+
50+const SchemaContext = createContext<FxSchema<any, any> | null>(null);
51+
52+export function Provider(
53+ { store, schema, children }: {
54+ store: FxStore<any>;
55+ schema: FxSchema<any, any>;
56+ children: React.ReactNode;
57+ },
58+) {
59+ return (
60+ h(ReduxProvider, {
61+ store,
62+ children: h(
63+ SchemaContext.Provider,
64+ { value: schema, children },
65+ ) as any,
66+ })
67+ );
68+}
69+
70+export function useSchema<S extends AnyState>() {
71+ return useContext(SchemaContext) as FxSchema<S>;
72+}
73+
74+/**
75+ * useLoader will take an action creator or action itself and return the associated
76+ * loader for it.
77+ *
78+ * @returns the loader object for an action creator or action
79+ *
80+ * @example
81+ * ```ts
82+ * import { useLoader } from 'starfx/react';
83+ *
84+ * import { api } from './api';
85+ *
86+ * const fetchUsers = api.get('/users', function*() {
87+ * // ...
88+ * });
89+ *
90+ * const View = () => {
91+ * const loader = useLoader(fetchUsers);
92+ * // or: const loader = useLoader(fetchUsers());
93+ * return <div>{loader.isLoader ? 'Loading ...' : 'Done!'}</div>
94+ * }
95+ * ```
96+ */
97+export function useLoader<S extends AnyState>(
98+ action: ThunkAction | ActionFn,
99+) {
100+ const schema = useSchema();
101+ const id = typeof action === "function" ? `${action}` : action.payload.key;
102+ return useSelector((s: S) => schema.loaders.selectById(s, { id }));
103+}
104+
105+/**
106+ * useApi will take an action creator or action itself and fetch
107+ * the associated loader and create a `trigger` function that you can call
108+ * later in your react component.
109+ *
110+ * This hook will *not* fetch the data for you because it does not know how to fetch
111+ * data from your redux state.
112+ *
113+ * @example
114+ * ```ts
115+ * import { useApi } from 'starfx/react';
116+ *
117+ * import { api } from './api';
118+ *
119+ * const fetchUsers = api.get('/users', function*() {
120+ * // ...
121+ * });
122+ *
123+ * const View = () => {
124+ * const { isLoading, trigger } = useApi(fetchUsers);
125+ * useEffect(() => {
126+ * trigger();
127+ * }, []);
128+ * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
129+ * }
130+ * ```
131+ */
132+export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
133+ action: A,
134+): UseApiAction<A>;
135+export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
136+ action: ActionFn<P>,
137+): UseApiProps<P>;
138+export function useApi<A extends ThunkAction = ThunkAction>(
139+ action: ActionFnSimple,
140+): UseApiSimpleProps;
141+export function useApi(action: any): any {
142+ const dispatch = useDispatch();
143+ const loader = useLoader(action);
144+ const trigger = (p: any) => {
145+ if (typeof action === "function") {
146+ dispatch(action(p));
147+ } else {
148+ dispatch(action);
149+ }
150+ };
151+ return { ...loader, trigger, action };
152+}
153+
154+/**
155+ * useQuery uses {@link useApi} and automatically calls `useApi().trigger()`
156+ *
157+ * @example
158+ * ```ts
159+ * import { useQuery } from 'starfx/react';
160+ *
161+ * import { api } from './api';
162+ *
163+ * const fetchUsers = api.get('/users', function*() {
164+ * // ...
165+ * });
166+ *
167+ * const View = () => {
168+ * const { isLoading } = useQuery(fetchUsers);
169+ * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
170+ * }
171+ * ```
172+ */
173+export function useQuery<P = any, A extends ThunkAction = ThunkAction<P>>(
174+ action: A,
175+): UseApiAction<A> {
176+ const api = useApi(action);
177+ useEffect(() => {
178+ api.trigger();
179+ }, [action.payload.key]);
180+ return api;
181+}
182+
183+/**
184+ * useCache uses {@link useQuery} and automatically selects the cached data associated
185+ * with the action creator or action provided.
186+ *
187+ * @example
188+ * ```ts
189+ * import { useCache } from 'starfx/react';
190+ *
191+ * import { api } from './api';
192+ *
193+ * const fetchUsers = api.get('/users', api.cache());
194+ *
195+ * const View = () => {
196+ * const { isLoading, data } = useCache(fetchUsers());
197+ * return <div>{isLoading ? : 'Loading' : data.length}</div>
198+ * }
199+ * ```
200+ */
201+export function useCache<P = any, ApiSuccess = any>(
202+ action: ThunkAction<P, ApiSuccess>,
203+): UseCacheResult<typeof action.payload._result, ThunkAction<P, ApiSuccess>> {
204+ const schema = useSchema();
205+ const id = action.payload.key;
206+ const data: any = useSelector((s: any) => schema.cache.selectById(s, { id }));
207+ const query = useQuery(action);
208+ return { ...query, data: data || null };
209+}
210+
211+/**
212+ * useLoaderSuccess will activate the callback provided when the loader transitions
213+ * from some state to success.
214+ *
215+ * @example
216+ * ```ts
217+ * import { useLoaderSuccess, useApi } from 'starfx/react';
218+ *
219+ * import { api } from './api';
220+ *
221+ * const createUser = api.post('/users', function*(ctx, next) {
222+ * // ...
223+ * });
224+ *
225+ * const View = () => {
226+ * const { loader, trigger } = useApi(createUser);
227+ * const onSubmit = () => {
228+ * trigger({ name: 'bob' });
229+ * };
230+ *
231+ * useLoaderSuccess(loader, () => {
232+ * // success!
233+ * // Use this callback to navigate to another view
234+ * });
235+ *
236+ * return <button onClick={onSubmit}>Create user!</button>
237+ * }
238+ * ```
239+ */
240+export function useLoaderSuccess(
241+ cur: Pick<LoaderState, "status">,
242+ success: () => any,
243+) {
244+ const prev = useRef(cur);
245+ useEffect(() => {
246+ if (prev.current.status !== "success" && cur.status === "success") {
247+ success();
248+ }
249+ prev.current = cur;
250+ }, [cur.status]);
251+}
252+
253+interface PersistGateProps {
254+ children: React.ReactNode;
255+ loading?: JSX.Element;
256+}
257+
258+function Loading({ text }: { text: string }) {
259+ return h("div", null, text);
260+}
261+
262+export function PersistGate(
263+ { children, loading = h(Loading) }: PersistGateProps,
264+) {
265+ const schema = useSchema();
266+ const ldr = useSelector((s: any) =>
267+ schema.loaders.selectById(s, { id: PERSIST_LOADER_ID })
268+ );
269+
270+ if (ldr.status === "error") {
271+ return h("div", null, ldr.message);
272+ }
273+
274+ if (ldr.status !== "success") {
275+ return loading;
276+ }
277+
278+ return children;
279+}
+1,
-2
1@@ -1,7 +1,6 @@
2 import { action } from "../deps.ts";
3-import { Next } from "../query/types.ts";
4 import { UpdaterCtx } from "./types.ts";
5-import { AnyState } from "../types.ts";
6+import { AnyState, Next } from "../types.ts";
7
8 export function createBatchMdw<S extends AnyState>(
9 queue: (send: () => void) => void,
+3,
-4
1@@ -1,8 +1,7 @@
2-import { Err, Ok, Operation, Result } from "../deps.ts";
3-import { Next } from "../query/types.ts";
4-import { AnyState } from "../types.ts";
5+import { Err, Ok, type Operation, type Result } from "../deps.ts";
6+import type { AnyState, Next } from "../types.ts";
7 import { select, updateStore } from "./fx.ts";
8-import { UpdaterCtx } from "./types.ts";
9+import type { UpdaterCtx } from "./types.ts";
10
11 export const PERSIST_LOADER_ID = "@@starfx/persist";
12
+2,
-2
1@@ -1,6 +1,6 @@
2-import type { ApiCtx, Next, ThunkCtx } from "../query/mod.ts";
3+import type { ApiCtx, ThunkCtx } from "../query/mod.ts";
4 import { compose } from "../compose.ts";
5-import type { AnyAction, AnyState } from "../types.ts";
6+import type { AnyAction, AnyState, Next } from "../types.ts";
7 import { put } from "../action.ts";
8 import { select, updateStore } from "./fx.ts";
9 import { LoaderOutput } from "./slice/loader.ts";
+0,
-270
1@@ -1,270 +0,0 @@
2-import { PERSIST_LOADER_ID } from "./persist.ts";
3-import type { LoaderOutput } from "./slice/mod.ts";
4-import type { AnyState, LoaderState } from "../types.ts";
5-import {
6- Provider as ReduxProvider,
7- React,
8- useDispatch,
9- useSelector,
10-} from "../deps.ts";
11-import { ThunkAction } from "../query/types.ts";
12-import type { FxSchema, FxStore } from "./types.ts";
13-const { useEffect, useRef } = React;
14-
15-type ActionFn<P = any> = (p: P) => { toString: () => string };
16-type ActionFnSimple = () => { toString: () => string };
17-
18-export interface UseApiProps<P = any> extends LoaderState {
19- trigger: (p: P) => void;
20- action: ActionFn<P>;
21-}
22-export interface UseApiSimpleProps extends LoaderState {
23- trigger: () => void;
24- action: ActionFn;
25-}
26-export interface UseApiAction<A extends ThunkAction = ThunkAction>
27- extends LoaderState {
28- trigger: () => void;
29- action: A;
30-}
31-export type UseApiResult<P, A extends ThunkAction = ThunkAction> =
32- | UseApiProps<P>
33- | UseApiSimpleProps
34- | UseApiAction<A>;
35-
36-export interface UseCacheResult<D = any, A extends ThunkAction = ThunkAction>
37- extends UseApiAction<A> {
38- data: D | null;
39-}
40-
41-const SchemaContext = React.createContext<FxSchema<any> | null>(null);
42-
43-export function Provider<S extends AnyState>(
44- { store, schema, children }: {
45- store: FxStore<S>;
46- schema: FxSchema<S>;
47- children: React.ReactNode;
48- },
49-) {
50- return (
51- React.createElement(ReduxProvider, {
52- store,
53- children: React.createElement(
54- SchemaContext.Provider,
55- { value: schema, children },
56- ) as any,
57- })
58- );
59-}
60-
61-export function useSchema<S extends AnyState>() {
62- return React.useContext(SchemaContext) as FxSchema<S>;
63-}
64-
65-/**
66- * useLoader will take an action creator or action itself and return the associated
67- * loader for it.
68- *
69- * @returns the loader object for an action creator or action
70- *
71- * @example
72- * ```ts
73- * import { useLoader } from 'starfx/react';
74- *
75- * import { api } from './api';
76- *
77- * const fetchUsers = api.get('/users', function*() {
78- * // ...
79- * });
80- *
81- * const View = () => {
82- * const loader = useLoader(fetchUsers);
83- * // or: const loader = useLoader(fetchUsers());
84- * return <div>{loader.isLoader ? 'Loading ...' : 'Done!'}</div>
85- * }
86- * ```
87- */
88-export function useLoader<S extends AnyState>(
89- action: ThunkAction | ActionFn,
90-) {
91- const schema = useSchema();
92- const id = typeof action === "function" ? `${action}` : action.payload.key;
93- return useSelector((s: S) => schema.loaders.selectById(s, { id }));
94-}
95-
96-/**
97- * useApi will take an action creator or action itself and fetch
98- * the associated loader and create a `trigger` function that you can call
99- * later in your react component.
100- *
101- * This hook will *not* fetch the data for you because it does not know how to fetch
102- * data from your redux state.
103- *
104- * @example
105- * ```ts
106- * import { useApi } from 'starfx/react';
107- *
108- * import { api } from './api';
109- *
110- * const fetchUsers = api.get('/users', function*() {
111- * // ...
112- * });
113- *
114- * const View = () => {
115- * const { isLoading, trigger } = useApi(fetchUsers);
116- * useEffect(() => {
117- * trigger();
118- * }, []);
119- * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
120- * }
121- * ```
122- */
123-export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
124- action: A,
125-): UseApiAction<A>;
126-export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
127- action: ActionFn<P>,
128-): UseApiProps<P>;
129-export function useApi<A extends ThunkAction = ThunkAction>(
130- action: ActionFnSimple,
131-): UseApiSimpleProps;
132-export function useApi(action: any): any {
133- const dispatch = useDispatch();
134- const loader = useLoader(action);
135- const trigger = (p: any) => {
136- if (typeof action === "function") {
137- dispatch(action(p));
138- } else {
139- dispatch(action);
140- }
141- };
142- return { ...loader, trigger, action };
143-}
144-
145-/**
146- * useQuery uses {@link useApi} and automatically calls `useApi().trigger()`
147- *
148- * @example
149- * ```ts
150- * import { useQuery } from 'starfx/react';
151- *
152- * import { api } from './api';
153- *
154- * const fetchUsers = api.get('/users', function*() {
155- * // ...
156- * });
157- *
158- * const View = () => {
159- * const { isLoading } = useQuery(fetchUsers);
160- * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
161- * }
162- * ```
163- */
164-export function useQuery<P = any, A extends ThunkAction = ThunkAction<P>>(
165- action: A,
166-): UseApiAction<A> {
167- const api = useApi(action);
168- useEffect(() => {
169- api.trigger();
170- }, [action.payload.key]);
171- return api;
172-}
173-
174-/**
175- * useCache uses {@link useQuery} and automatically selects the cached data associated
176- * with the action creator or action provided.
177- *
178- * @example
179- * ```ts
180- * import { useCache } from 'starfx/react';
181- *
182- * import { api } from './api';
183- *
184- * const fetchUsers = api.get('/users', api.cache());
185- *
186- * const View = () => {
187- * const { isLoading, data } = useCache(fetchUsers());
188- * return <div>{isLoading ? : 'Loading' : data.length}</div>
189- * }
190- * ```
191- */
192-export function useCache<P = any, ApiSuccess = any>(
193- action: ThunkAction<P, ApiSuccess>,
194-): UseCacheResult<typeof action.payload._result, ThunkAction<P, ApiSuccess>> {
195- const schema = useSchema();
196- const id = action.payload.key;
197- const data: any = useSelector((s: any) => schema.cache.selectById(s, { id }));
198- const query = useQuery(action);
199- return { ...query, data: data || null };
200-}
201-
202-/**
203- * useLoaderSuccess will activate the callback provided when the loader transitions
204- * from some state to success.
205- *
206- * @example
207- * ```ts
208- * import { useLoaderSuccess, useApi } from 'starfx/react';
209- *
210- * import { api } from './api';
211- *
212- * const createUser = api.post('/users', function*(ctx, next) {
213- * // ...
214- * });
215- *
216- * const View = () => {
217- * const { loader, trigger } = useApi(createUser);
218- * const onSubmit = () => {
219- * trigger({ name: 'bob' });
220- * };
221- *
222- * useLoaderSuccess(loader, () => {
223- * // success!
224- * // Use this callback to navigate to another view
225- * });
226- *
227- * return <button onClick={onSubmit}>Create user!</button>
228- * }
229- * ```
230- */
231-export function useLoaderSuccess(
232- cur: Pick<LoaderState, "status">,
233- success: () => any,
234-) {
235- const prev = useRef(cur);
236- useEffect(() => {
237- if (prev.current.status !== "success" && cur.status === "success") {
238- success();
239- }
240- prev.current = cur;
241- }, [cur.status]);
242-}
243-
244-interface PersistGateProps {
245- children: React.ReactNode;
246- loading?: JSX.Element;
247- loader: LoaderOutput<any, any>;
248-}
249-
250-function Loading({ text }: { text: string }) {
251- return React.createElement("div", null, text);
252-}
253-
254-export function PersistGate(
255- { children, loading = React.createElement(Loading) }: PersistGateProps,
256-) {
257- const schema = useSchema();
258- const ldr = useSelector((s: any) =>
259- schema.loaders.selectById(s, { id: PERSIST_LOADER_ID })
260- );
261-
262- if (ldr.status === "error") {
263- return React.createElement("div", null, ldr.message);
264- }
265-
266- if (ldr.status !== "success") {
267- return loading;
268- }
269-
270- return children;
271-}
+1,
-2
1@@ -10,9 +10,8 @@ import {
2 Task,
3 } from "../deps.ts";
4 import { BaseMiddleware, compose } from "../compose.ts";
5-import type { AnyAction, AnyState } from "../types.ts";
6+import type { AnyAction, AnyState, Next } from "../types.ts";
7 import { safe } from "../fx/mod.ts";
8-import { Next } from "../query/types.ts";
9 import type { FxStore, Listener, StoreUpdater, UpdaterCtx } from "./types.ts";
10 import { StoreContext, StoreUpdateContext } from "./context.ts";
11 import { log } from "../log.ts";
+16,
-118
1@@ -1,122 +1,20 @@
2-/* import {
3- cleanup,
4- fireEvent,
5- render,
6- screen,
7-} from "https://esm.sh/@testing-library/react@14.0.0?pin=v122";
8-
9+import { asserts, describe, it } from "../test.ts";
10+import { Provider } from "../react.ts";
11+import { createSchema, createStore, slice } from "../store/mod.ts";
12 import { React } from "../deps.ts";
13-import { asserts, beforeEach, describe, it } from "../test.ts";
14-import { Provider, sleep as delay, useSelector } from "../deps.ts";
15-import { configureStore, updateStore } from "../store/mod.ts";
16-
17-import { createApi } from "./api.ts";
18-import * as mdw from "./middleware.ts";
19-import { useApi } from "./react.ts";
20-import { selectDataById } from "./slice.ts";
21-import { createKey } from "./create-key.ts";
22-
23-const h = React.createElement;
24-
25-const mockUser = { id: "1", email: "test@starfx.com" };
26-
27-const jsonBlob = (data: any) => {
28- return JSON.stringify(data);
29-};
30-
31-interface User {
32- id: string;
33- name: string;
34- email: string;
35-}
36-
37-const setupTest = async () => {
38- const api = createApi();
39- api.use(mdw.api());
40- api.use(api.routes());
41- api.use(function* (ctx, next) {
42- yield* delay(10);
43- ctx.json = { ok: true, data: mockUser };
44- ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
45- yield* next();
46- });
47-
48- const fetchUser = api.get<{ id: string }>("/user/:id", function* (ctx, next) {
49- ctx.cache = true;
50- yield* next();
51- if (!ctx.json.ok) return;
52- yield* updateStore<{ user: User }>((state) => {
53- state.user = ctx.json.data;
54- });
55- });
56-
57- const store = configureStore<{ user?: User }>({
58- initialState: {},
59- });
60- store.run(api.bootup);
61-
62- return { store, fetchUser, api };
63-};
64-
65-describe.ignore("useApi()", () => {
66- beforeEach(() => cleanup());
67- it("with action", async () => {
68- const { fetchUser, store } = await setupTest();
69- const App = () => {
70- const action = fetchUser({ id: "1" });
71- const query = useApi(action);
72- const user = useSelector((s: any) =>
73- selectDataById(s, { id: action.payload.key })
74- ) as User;
75-
76- return h("div", null, [
77- h("div", { key: "1" }, user?.email || ""),
78- h(
79- "button",
80- { key: "2", onClick: () => query.trigger() },
81- query.isLoading ? "loading" : "fetch",
82- ),
83- h("div", { key: "3" }, query.isSuccess ? "success" : ""),
84- ]);
85- };
86- // render(h(Provider, { store, children: h(App) }));
87-
88- const button = screen.getByText("fetch");
89- fireEvent.click(button);
90-
91- await screen.findByText("loading");
92- await screen.findByText(mockUser.email);
93- await screen.findByText("success");
94- asserts.assert(true);
95- });
96-
97- it("with action creator", async () => {
98- const { fetchUser, store } = await setupTest();
99- const App = () => {
100- const query = useApi(fetchUser);
101- const user = useSelector((s: any) => {
102- const id = createKey(`${fetchUser}`, { id: "1" });
103- return selectDataById(s, { id });
104- }) as User;
105-
106- return h("div", null, [
107- h("div", { key: "1" }, user?.email || "no user"),
108- h(
109- "button",
110- { key: "2", onClick: () => query.trigger({ id: "1" }) },
111- query.isLoading ? "loading" : "fetch",
112- ),
113- h("div", { key: "3" }, query.isSuccess ? "success" : ""),
114- ]);
115- };
116- // render(h(Provider, { store, children: h(App) }));
117
118- const button = screen.getByText("fetch");
119- fireEvent.click(button);
120+const tests = describe("react");
121
122- await screen.findByText("loading");
123- await screen.findByText(mockUser.email);
124- await screen.findByText("success");
125- asserts.assert(true);
126+// typing test
127+it(tests, () => {
128+ const [schema, initialState] = createSchema({
129+ cache: slice.table(),
130+ loaders: slice.loader(),
131 });
132-}); */
133+ const store = createStore({ initialState });
134+ React.createElement(
135+ Provider,
136+ { schema, store, children: React.createElement("div") },
137+ );
138+ asserts.equal(true, true);
139+});
M
types.ts
+3,
-1
1@@ -1,10 +1,12 @@
2-import type { Instruction } from "./deps.ts";
3+import type { Instruction, Operation } from "./deps.ts";
4
5 export interface Computation<T = unknown> {
6 // deno-lint-ignore no-explicit-any
7 [Symbol.iterator](): Iterator<Instruction, T, any>;
8 }
9
10+export type Next = () => Operation<void>;
11+
12 export type IdProp = string | number;
13 export type LoadingStatus = "loading" | "success" | "error" | "idle";
14 export interface LoaderItemState<