@kirill.konshin/react 0.0.4 → 0.0.6
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/dist/apiCall.js +19 -21
- package/dist/apiCall.js.map +1 -1
- package/dist/form/client.d.ts +1 -1
- package/dist/form/client.d.ts.map +1 -1
- package/dist/form/client.js +46 -42
- package/dist/form/client.js.map +1 -1
- package/dist/form/form.d.ts +6 -6
- package/dist/form/form.d.ts.map +1 -1
- package/dist/form/form.js +82 -79
- package/dist/form/form.js.map +1 -1
- package/dist/form/index.d.ts +2 -2
- package/dist/form/index.js +3 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -21
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +38 -38
- package/dist/keyboard.js.map +1 -1
- package/dist/useFetch.js +83 -64
- package/dist/useFetch.js.map +1 -1
- package/dist/useFetchAction.js +35 -25
- package/dist/useFetchAction.js.map +1 -1
- package/package.json +12 -7
- package/.ctirc +0 -20
- package/.turbo/turbo-build.log +0 -30
- package/CHANGELOG.md +0 -20
- package/dist/index.js.map +0 -1
- package/src/apiCall.ts +0 -25
- package/src/form/client.tsx +0 -73
- package/src/form/form.tsx +0 -178
- package/src/form/index.ts +0 -2
- package/src/index.ts +0 -5
- package/src/keyboard.tsx +0 -73
- package/src/useFetch.test.tsx +0 -424
- package/src/useFetch.ts +0 -109
- package/src/useFetchAction.ts +0 -42
- package/tsconfig.json +0 -10
- package/turbo.json +0 -10
- package/vite.config.ts +0 -2
package/dist/keyboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.js","sources":["../src/keyboard.tsx"],"sourcesContent":["'use client';\n\nimport {\n useEffect,\n createContext,\n useMemo,\n useState,\n Dispatch,\n SetStateAction,\n useContext,\n FC,\n memo,\n Context,\n} from 'react';\n\nconst isCtrlOrMeta = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;\n\nconst EVENT = 'keydown';\n\nexport type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;\n\nexport type HotkeyContextType = {\n enabled: boolean;\n setEnabled: Dispatch<SetStateAction<boolean>>;\n};\n\nexport const HotkeysContext: Context<HotkeyContextType> = createContext<{\n enabled: boolean;\n setEnabled: Dispatch<SetStateAction<boolean>>;\n}>(null as never);\n\nexport type HotkeysProviderProps = {\n children: any;\n};\n\nexport const HotkeysProvider: FC<HotkeysProviderProps> = memo(function HotkeysProvider({ children }) {\n const [enabled, setEnabled] = useState(true);\n\n const control = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled]);\n\n return <HotkeysContext.Provider value={control}>{children}</HotkeysContext.Provider>;\n});\n\nexport const useHotkeys = (hotkeys: Hotkeys): void => {\n const { enabled } = useContext(HotkeysContext);\n\n useEffect(() => {\n if (typeof document === 'undefined' || !enabled) {\n return;\n }\n\n const ctrl = new AbortController();\n\n window.addEventListener(\n EVENT,\n (e: KeyboardEvent) => {\n if (!isCtrlOrMeta(e)) return;\n\n for (const [code, callback] of Object.entries(hotkeys)) {\n if (e.code === code) {\n callback(e);\n return;\n }\n }\n },\n { signal: ctrl.signal, capture: true },\n );\n\n return () => {\n ctrl.abort();\n };\n }, [hotkeys, enabled]);\n};\n"],"
|
|
1
|
+
{"version":3,"file":"keyboard.js","names":[],"sources":["../src/keyboard.tsx"],"sourcesContent":["'use client';\n\nimport {\n useEffect,\n createContext,\n useMemo,\n useState,\n type Dispatch,\n type SetStateAction,\n useContext,\n type FC,\n memo,\n type Context,\n} from 'react';\n\nconst isCtrlOrMeta = (e: KeyboardEvent) => e.metaKey || e.ctrlKey;\n\nconst EVENT = 'keydown';\n\nexport type Hotkeys = Record<KeyboardEvent['code'], (e: KeyboardEvent) => void>;\n\nexport type HotkeyContextType = {\n enabled: boolean;\n setEnabled: Dispatch<SetStateAction<boolean>>;\n};\n\nexport const HotkeysContext: Context<HotkeyContextType> = createContext<{\n enabled: boolean;\n setEnabled: Dispatch<SetStateAction<boolean>>;\n}>(null as never);\n\nexport type HotkeysProviderProps = {\n children: any;\n};\n\nexport const HotkeysProvider: FC<HotkeysProviderProps> = memo(function HotkeysProvider({ children }) {\n const [enabled, setEnabled] = useState(true);\n\n const control = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled]);\n\n return <HotkeysContext.Provider value={control}>{children}</HotkeysContext.Provider>;\n});\n\nexport const useHotkeys = (hotkeys: Hotkeys): void => {\n const { enabled } = useContext(HotkeysContext);\n\n useEffect(() => {\n if (typeof document === 'undefined' || !enabled) {\n return;\n }\n\n const ctrl = new AbortController();\n\n window.addEventListener(\n EVENT,\n (e: KeyboardEvent) => {\n if (!isCtrlOrMeta(e)) return;\n\n for (const [code, callback] of Object.entries(hotkeys)) {\n if (e.code === code) {\n callback(e);\n return;\n }\n }\n },\n { signal: ctrl.signal, capture: true },\n );\n\n return () => {\n ctrl.abort();\n };\n }, [hotkeys, enabled]);\n};\n"],"mappings":";;;;AAeA,IAAM,gBAAgB,MAAqB,EAAE,WAAW,EAAE;AAE1D,IAAM,QAAQ;AASd,IAAa,iBAA6C,cAGvD,IAAa;AAMhB,IAAa,kBAA4C,KAAK,SAAS,gBAAgB,EAAE,YAAY;CACjG,MAAM,CAAC,SAAS,cAAc,SAAS,IAAI;CAE3C,MAAM,UAAU,eAAe;EAAE;EAAS;CAAW,IAAI,CAAC,SAAS,UAAU,CAAC;CAE9E,OAAO,oBAAC,eAAe,UAAhB;EAAyB,OAAO;EAAU;CAAkC,CAAA;AACvF,CAAC;AAED,IAAa,cAAc,YAA2B;CAClD,MAAM,EAAE,YAAY,WAAW,cAAc;CAE7C,gBAAgB;EACZ,IAAI,OAAO,aAAa,eAAe,CAAC,SACpC;EAGJ,MAAM,OAAO,IAAI,gBAAgB;EAEjC,OAAO,iBACH,QACC,MAAqB;GAClB,IAAI,CAAC,aAAa,CAAC,GAAG;GAEtB,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,OAAO,GACjD,IAAI,EAAE,SAAS,MAAM;IACjB,SAAS,CAAC;IACV;GACJ;EAER,GACA;GAAE,QAAQ,KAAK;GAAQ,SAAS;EAAK,CACzC;EAEA,aAAa;GACT,KAAK,MAAM;EACf;CACJ,GAAG,CAAC,SAAS,OAAO,CAAC;AACzB"}
|
package/dist/useFetch.js
CHANGED
|
@@ -1,66 +1,85 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, useTransition } from "react";
|
|
3
|
+
//#region src/useFetch.ts
|
|
4
|
+
var unmountError = "Component is unmounted after fetch completed";
|
|
5
|
+
/**
|
|
6
|
+
* TODO useFetch https://use-http.com
|
|
7
|
+
* TODO SWR?
|
|
8
|
+
* TODO Tanstack Query?
|
|
9
|
+
*
|
|
10
|
+
* Uses same return array patterns as useActionState https://react.dev/reference/react/useActionState + error,
|
|
11
|
+
* reason: simple var rename
|
|
12
|
+
*
|
|
13
|
+
* Function can be async, then it will be awaited and result set to state.
|
|
14
|
+
*
|
|
15
|
+
* Function can be sync, then it will be called args, and it should return another function,which will be called with
|
|
16
|
+
* old data and result set to state. This is useful for pagination and merging data.
|
|
17
|
+
*
|
|
18
|
+
* @param {(...args: any[]) => Promise<R> | R | ((oldData: R) => Promise<R> | R))} fn
|
|
19
|
+
* @param {any} defaultValue
|
|
20
|
+
* @param fetchOnMount
|
|
21
|
+
* @param throwAfterUnmount - throw if component is unmounted after fetch completed
|
|
22
|
+
* @returns {[R, (...args: any[]) => Promise<R>, boolean, Error | undefined]} //, ReturnType<typeof useState>, ReturnType<typeof useState>
|
|
23
|
+
*/
|
|
24
|
+
function useFetch(fn, defaultValue, { fetchOnMount = false, throwAfterUnmount = false } = {}) {
|
|
25
|
+
const [isPending, startTransition] = useTransition();
|
|
26
|
+
const [data, setData] = useState(defaultValue);
|
|
27
|
+
const [error, setError] = useState();
|
|
28
|
+
const [loading, setLoading] = useState(fetchOnMount);
|
|
29
|
+
const isMounted = useRef(false);
|
|
30
|
+
const oldData = useRef(data);
|
|
31
|
+
const throwAfterUnmountRef = useRef(throwAfterUnmount);
|
|
32
|
+
const actionFn = useCallback((...args) => {
|
|
33
|
+
const res = fn(...args);
|
|
34
|
+
const promise = typeof res === "function" ? res(oldData.current) : res;
|
|
35
|
+
startTransition(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const newData = await promise;
|
|
38
|
+
if (!isMounted.current) {
|
|
39
|
+
if (throwAfterUnmountRef.current) throw new Error(unmountError);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
oldData.current = newData;
|
|
43
|
+
setData(newData);
|
|
44
|
+
setError(void 0);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
if (!isMounted.current) {
|
|
47
|
+
if (throwAfterUnmountRef.current) if (e.message !== unmountError) throw new Error("Component is unmounted", { cause: e });
|
|
48
|
+
else throw e;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setError(e);
|
|
52
|
+
} finally {
|
|
53
|
+
if (isMounted.current) setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return promise;
|
|
57
|
+
}, [fn]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!fetchOnMount) return;
|
|
60
|
+
actionFn().catch((e) => console.error("Fetch on mount failed", e));
|
|
61
|
+
}, [
|
|
62
|
+
fetchOnMount,
|
|
63
|
+
fn,
|
|
64
|
+
actionFn
|
|
65
|
+
]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
throwAfterUnmountRef.current = throwAfterUnmount;
|
|
68
|
+
}, [throwAfterUnmount]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
isMounted.current = true;
|
|
71
|
+
return () => {
|
|
72
|
+
isMounted.current = false;
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
return [
|
|
76
|
+
data,
|
|
77
|
+
actionFn,
|
|
78
|
+
isPending || loading,
|
|
79
|
+
error
|
|
80
|
+
];
|
|
62
81
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//# sourceMappingURL=useFetch.js.map
|
|
82
|
+
//#endregion
|
|
83
|
+
export { useFetch };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=useFetch.js.map
|
package/dist/useFetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFetch.js","sources":["../src/useFetch.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState, useTransition } from 'react';\n\nconst unmountError = 'Component is unmounted after fetch completed';\n\n/**\n * TODO useFetch https://use-http.com\n * TODO SWR?\n * TODO Tanstack Query?\n *\n * Uses same return array patterns as useActionState https://react.dev/reference/react/useActionState + error,\n * reason: simple var rename\n *\n * Function can be async, then it will be awaited and result set to state.\n *\n * Function can be sync, then it will be called args, and it should return another function,which will be called with\n * old data and result set to state. This is useful for pagination and merging data.\n *\n * @param {(...args: any[]) => Promise<R> | R | ((oldData: R) => Promise<R> | R))} fn\n * @param {any} defaultValue\n * @param fetchOnMount\n * @param throwAfterUnmount - throw if component is unmounted after fetch completed\n * @returns {[R, (...args: any[]) => Promise<R>, boolean, Error | undefined]} //, ReturnType<typeof useState>, ReturnType<typeof useState>\n */\nexport function useFetch<R>(\n fn: (...args: any[]) => Promise<R> | R | ((oldData: R) => Promise<R> | R),\n defaultValue: R,\n {\n fetchOnMount = false,\n throwAfterUnmount = false,\n }: {\n fetchOnMount?: boolean;\n throwAfterUnmount?: boolean;\n } = {},\n): [\n R,\n (...args: Parameters<typeof fn>) => Promise<R>,\n boolean,\n Error | undefined,\n // ReturnType<typeof useState<R>>[1],\n // ReturnType<typeof useState<Error>>[1],\n] {\n // An async function was passed to useActionState, but it was dispatched outside of an action context.\n // This is likely not what you intended. Either pass the dispatch function to an `action` prop, or dispatch manually inside `startTransition`\n const [isPending, startTransition] = useTransition();\n const [data, setData] = useState<R>(defaultValue);\n const [error, setError] = useState<Error>();\n const [loading, setLoading] = useState(fetchOnMount);\n const isMounted = useRef(false);\n const oldData = useRef(data);\n const throwAfterUnmountRef = useRef(throwAfterUnmount);\n\n const actionFn = useCallback(\n (...args: Parameters<typeof fn>) => {\n const res = fn(...args);\n\n const promise: Promise<R> = typeof res === 'function' ? (res as any)(oldData.current) : (res as Promise<R>);\n\n // https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition\n startTransition(async () => {\n try {\n const newData = await promise;\n if (!isMounted.current) {\n if (throwAfterUnmountRef.current) throw new Error(unmountError);\n return;\n }\n oldData.current = newData;\n setData(newData);\n setError(undefined);\n } catch (e) {\n if (!isMounted.current) {\n if (throwAfterUnmountRef.current) {\n if (e.message !== unmountError) throw new Error('Component is unmounted', { cause: e });\n else throw e;\n }\n return;\n }\n setError(e);\n } finally {\n if (isMounted.current) {\n setLoading(false);\n }\n }\n });\n\n return promise;\n },\n [fn],\n );\n\n useEffect(() => {\n if (!fetchOnMount) return;\n actionFn().catch((e) => console.error('Fetch on mount failed', e)); // catch actually will never happen\n }, [fetchOnMount, fn, actionFn]);\n\n useEffect(() => {\n throwAfterUnmountRef.current = throwAfterUnmount;\n }, [throwAfterUnmount]);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n });\n\n return [data, actionFn, isPending || loading, error]; // , setData, setError\n}\n"],"
|
|
1
|
+
{"version":3,"file":"useFetch.js","names":[],"sources":["../src/useFetch.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState, useTransition } from 'react';\n\nconst unmountError = 'Component is unmounted after fetch completed';\n\n/**\n * TODO useFetch https://use-http.com\n * TODO SWR?\n * TODO Tanstack Query?\n *\n * Uses same return array patterns as useActionState https://react.dev/reference/react/useActionState + error,\n * reason: simple var rename\n *\n * Function can be async, then it will be awaited and result set to state.\n *\n * Function can be sync, then it will be called args, and it should return another function,which will be called with\n * old data and result set to state. This is useful for pagination and merging data.\n *\n * @param {(...args: any[]) => Promise<R> | R | ((oldData: R) => Promise<R> | R))} fn\n * @param {any} defaultValue\n * @param fetchOnMount\n * @param throwAfterUnmount - throw if component is unmounted after fetch completed\n * @returns {[R, (...args: any[]) => Promise<R>, boolean, Error | undefined]} //, ReturnType<typeof useState>, ReturnType<typeof useState>\n */\nexport function useFetch<R>(\n fn: (...args: any[]) => Promise<R> | R | ((oldData: R) => Promise<R> | R),\n defaultValue: R,\n {\n fetchOnMount = false,\n throwAfterUnmount = false,\n }: {\n fetchOnMount?: boolean;\n throwAfterUnmount?: boolean;\n } = {},\n): [\n R,\n (...args: Parameters<typeof fn>) => Promise<R>,\n boolean,\n Error | undefined,\n // ReturnType<typeof useState<R>>[1],\n // ReturnType<typeof useState<Error>>[1],\n] {\n // An async function was passed to useActionState, but it was dispatched outside of an action context.\n // This is likely not what you intended. Either pass the dispatch function to an `action` prop, or dispatch manually inside `startTransition`\n const [isPending, startTransition] = useTransition();\n const [data, setData] = useState<R>(defaultValue);\n const [error, setError] = useState<Error>();\n const [loading, setLoading] = useState(fetchOnMount);\n const isMounted = useRef(false);\n const oldData = useRef(data);\n const throwAfterUnmountRef = useRef(throwAfterUnmount);\n\n const actionFn = useCallback(\n (...args: Parameters<typeof fn>) => {\n const res = fn(...args);\n\n const promise: Promise<R> = typeof res === 'function' ? (res as any)(oldData.current) : (res as Promise<R>);\n\n // https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition\n startTransition(async () => {\n try {\n const newData = await promise;\n if (!isMounted.current) {\n if (throwAfterUnmountRef.current) throw new Error(unmountError);\n return;\n }\n oldData.current = newData;\n setData(newData);\n setError(undefined);\n } catch (e) {\n if (!isMounted.current) {\n if (throwAfterUnmountRef.current) {\n if (e.message !== unmountError) throw new Error('Component is unmounted', { cause: e });\n else throw e;\n }\n return;\n }\n setError(e);\n } finally {\n if (isMounted.current) {\n setLoading(false);\n }\n }\n });\n\n return promise;\n },\n [fn],\n );\n\n useEffect(() => {\n if (!fetchOnMount) return;\n actionFn().catch((e) => console.error('Fetch on mount failed', e)); // catch actually will never happen\n }, [fetchOnMount, fn, actionFn]);\n\n useEffect(() => {\n throwAfterUnmountRef.current = throwAfterUnmount;\n }, [throwAfterUnmount]);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n });\n\n return [data, actionFn, isPending || loading, error]; // , setData, setError\n}\n"],"mappings":";;;AAIA,IAAM,eAAe;;;;;;;;;;;;;;;;;;;;AAqBrB,SAAgB,SACZ,IACA,cACA,EACI,eAAe,OACf,oBAAoB,UAIpB,CAAC,GAQP;CAGE,MAAM,CAAC,WAAW,mBAAmB,cAAc;CACnD,MAAM,CAAC,MAAM,WAAW,SAAY,YAAY;CAChD,MAAM,CAAC,OAAO,YAAY,SAAgB;CAC1C,MAAM,CAAC,SAAS,cAAc,SAAS,YAAY;CACnD,MAAM,YAAY,OAAO,KAAK;CAC9B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,uBAAuB,OAAO,iBAAiB;CAErD,MAAM,WAAW,aACZ,GAAG,SAAgC;EAChC,MAAM,MAAM,GAAG,GAAG,IAAI;EAEtB,MAAM,UAAsB,OAAO,QAAQ,aAAc,IAAY,QAAQ,OAAO,IAAK;EAGzF,gBAAgB,YAAY;GACxB,IAAI;IACA,MAAM,UAAU,MAAM;IACtB,IAAI,CAAC,UAAU,SAAS;KACpB,IAAI,qBAAqB,SAAS,MAAM,IAAI,MAAM,YAAY;KAC9D;IACJ;IACA,QAAQ,UAAU;IAClB,QAAQ,OAAO;IACf,SAAS,KAAA,CAAS;GACtB,SAAS,GAAG;IACR,IAAI,CAAC,UAAU,SAAS;KACpB,IAAI,qBAAqB,SACrB,IAAI,EAAE,YAAY,cAAc,MAAM,IAAI,MAAM,0BAA0B,EAAE,OAAO,EAAE,CAAC;UACjF,MAAM;KAEf;IACJ;IACA,SAAS,CAAC;GACd,UAAU;IACN,IAAI,UAAU,SACV,WAAW,KAAK;GAExB;EACJ,CAAC;EAED,OAAO;CACX,GACA,CAAC,EAAE,CACP;CAEA,gBAAgB;EACZ,IAAI,CAAC,cAAc;EACnB,SAAS,CAAC,CAAC,OAAO,MAAM,QAAQ,MAAM,yBAAyB,CAAC,CAAC;CACrE,GAAG;EAAC;EAAc;EAAI;CAAQ,CAAC;CAE/B,gBAAgB;EACZ,qBAAqB,UAAU;CACnC,GAAG,CAAC,iBAAiB,CAAC;CAEtB,gBAAgB;EACZ,UAAU,UAAU;EACpB,aAAa;GACT,UAAU,UAAU;EACxB;CACJ,CAAC;CAED,OAAO;EAAC;EAAM;EAAU,aAAa;EAAS;CAAK;AACvD"}
|
package/dist/useFetchAction.js
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
import { startTransition, useActionState, useCallback, useEffect, useState } from "react";
|
|
3
|
+
//#region src/useFetchAction.ts
|
|
4
|
+
/**
|
|
5
|
+
* @param {(state: Awaited<R>, payload?: any) => Promise<R> | R} action
|
|
6
|
+
* @param {R} defaultValue
|
|
7
|
+
* @param {boolean} fetchOnMount
|
|
8
|
+
* @param {string} permalink
|
|
9
|
+
* @returns {[R, (payload?: P) => Promise<R> | R, boolean]}>
|
|
10
|
+
*/
|
|
11
|
+
function useFetchAction(action, defaultValue, { fetchOnMount = false, permalink = void 0 } = {}) {
|
|
12
|
+
const [data, dispatchAction, isPending] = useActionState(action, defaultValue, permalink);
|
|
13
|
+
const [loading, setLoading] = useState(fetchOnMount);
|
|
14
|
+
const dispatch = useCallback((payload) => {
|
|
15
|
+
startTransition(() => {
|
|
16
|
+
dispatchAction(payload);
|
|
17
|
+
setLoading(false);
|
|
18
|
+
});
|
|
19
|
+
}, [dispatchAction]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!fetchOnMount || !loading) return;
|
|
22
|
+
dispatch(void 0);
|
|
23
|
+
}, [
|
|
24
|
+
fetchOnMount,
|
|
25
|
+
dispatch,
|
|
26
|
+
loading
|
|
27
|
+
]);
|
|
28
|
+
return [
|
|
29
|
+
data,
|
|
30
|
+
dispatch,
|
|
31
|
+
isPending || loading
|
|
32
|
+
];
|
|
23
33
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=useFetchAction.js.map
|
|
34
|
+
//#endregion
|
|
35
|
+
export { useFetchAction };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=useFetchAction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFetchAction.js","sources":["../src/useFetchAction.ts"],"sourcesContent":["'use client';\n\nimport { useActionState, useCallback, useEffect, useState, startTransition } from 'react';\n\n/**\n * @param {(state: Awaited<R>, payload?: any) => Promise<R> | R} action\n * @param {R} defaultValue\n * @param {boolean} fetchOnMount\n * @param {string} permalink\n * @returns {[R, (payload?: P) => Promise<R> | R, boolean]}>\n */\nexport function useFetchAction<S, P = undefined>(\n action: (state: Awaited<S>, payload?: P) => S | Promise<S>,\n defaultValue: Awaited<S>,\n {\n fetchOnMount = false,\n permalink = undefined,\n }: {\n fetchOnMount?: boolean;\n permalink?: string;\n } = {},\n): [S, (payload: P) => void, boolean] {\n const [data, dispatchAction, isPending] = useActionState<S, P | undefined>(action, defaultValue, permalink);\n const [loading, setLoading] = useState(fetchOnMount);\n\n const dispatch = useCallback(\n (payload?: P) => {\n startTransition(() => {\n dispatchAction(payload);\n setLoading(false);\n });\n },\n [dispatchAction],\n );\n\n useEffect(() => {\n if (!fetchOnMount || !loading) return;\n dispatch(undefined);\n }, [fetchOnMount, dispatch, loading]);\n\n return [data, dispatch, isPending || loading];\n}\n"],"
|
|
1
|
+
{"version":3,"file":"useFetchAction.js","names":[],"sources":["../src/useFetchAction.ts"],"sourcesContent":["'use client';\n\nimport { useActionState, useCallback, useEffect, useState, startTransition } from 'react';\n\n/**\n * @param {(state: Awaited<R>, payload?: any) => Promise<R> | R} action\n * @param {R} defaultValue\n * @param {boolean} fetchOnMount\n * @param {string} permalink\n * @returns {[R, (payload?: P) => Promise<R> | R, boolean]}>\n */\nexport function useFetchAction<S, P = undefined>(\n action: (state: Awaited<S>, payload?: P) => S | Promise<S>,\n defaultValue: Awaited<S>,\n {\n fetchOnMount = false,\n permalink = undefined,\n }: {\n fetchOnMount?: boolean;\n permalink?: string;\n } = {},\n): [S, (payload: P) => void, boolean] {\n const [data, dispatchAction, isPending] = useActionState<S, P | undefined>(action, defaultValue, permalink);\n const [loading, setLoading] = useState(fetchOnMount);\n\n const dispatch = useCallback(\n (payload?: P) => {\n startTransition(() => {\n dispatchAction(payload);\n setLoading(false);\n });\n },\n [dispatchAction],\n );\n\n useEffect(() => {\n if (!fetchOnMount || !loading) return;\n dispatch(undefined);\n }, [fetchOnMount, dispatch, loading]);\n\n return [data, dispatch, isPending || loading];\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,eACZ,QACA,cACA,EACI,eAAe,OACf,YAAY,KAAA,MAIZ,CAAC,GAC6B;CAClC,MAAM,CAAC,MAAM,gBAAgB,aAAa,eAAiC,QAAQ,cAAc,SAAS;CAC1G,MAAM,CAAC,SAAS,cAAc,SAAS,YAAY;CAEnD,MAAM,WAAW,aACZ,YAAgB;EACb,sBAAsB;GAClB,eAAe,OAAO;GACtB,WAAW,KAAK;EACpB,CAAC;CACL,GACA,CAAC,cAAc,CACnB;CAEA,gBAAgB;EACZ,IAAI,CAAC,gBAAgB,CAAC,SAAS;EAC/B,SAAS,KAAA,CAAS;CACtB,GAAG;EAAC;EAAc;EAAU;CAAO,CAAC;CAEpC,OAAO;EAAC;EAAM;EAAU,aAAa;CAAO;AAChD"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirill.konshin/react",
|
|
3
3
|
"description": "Utilities",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"----- BUILD -----": "",
|
|
8
8
|
"clean": "rm -rf dist .tscache tsconfig.tsbuildinfo",
|
|
9
9
|
"build": "vite build",
|
|
10
|
-
"build:index": "ctix build",
|
|
11
|
-
"build:check-types": "attw --pack .",
|
|
12
10
|
"start": "yarn build --watch",
|
|
13
11
|
"wait": "wait-on ./dist/index.js",
|
|
14
12
|
"----- TEST -----": "",
|
|
15
|
-
"test
|
|
13
|
+
"test": "vitest run --coverage",
|
|
16
14
|
"test:watch": "vitest watch --coverage",
|
|
17
15
|
"----- STORYBOOK -----": "",
|
|
18
16
|
"storybook:start": "storybook dev -p 6006",
|
|
@@ -41,8 +39,7 @@
|
|
|
41
39
|
}
|
|
42
40
|
},
|
|
43
41
|
"publishConfig": {
|
|
44
|
-
"access": "public"
|
|
45
|
-
"provenance": true
|
|
42
|
+
"access": "public"
|
|
46
43
|
},
|
|
47
44
|
"author": "Kirill Konshin <kirill@konshin.org> (https://konshin.org)",
|
|
48
45
|
"license": "MIT",
|
|
@@ -67,5 +64,13 @@
|
|
|
67
64
|
"type": "git",
|
|
68
65
|
"url": "https://github.com/kirill-konshin/utils.git",
|
|
69
66
|
"directory": "packages/react"
|
|
70
|
-
}
|
|
67
|
+
},
|
|
68
|
+
"nx": {
|
|
69
|
+
"tags": [
|
|
70
|
+
"platform:react"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"dist"
|
|
75
|
+
]
|
|
71
76
|
}
|
package/.ctirc
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"options": [
|
|
3
|
-
{
|
|
4
|
-
"mode": "create",
|
|
5
|
-
"project": "tsconfig.json",
|
|
6
|
-
"include": "src/**/*.{ts,tsx}",
|
|
7
|
-
"exclude": [
|
|
8
|
-
"**/*.stories.*",
|
|
9
|
-
"**/*.test.*",
|
|
10
|
-
"**/*.fixture.*"
|
|
11
|
-
],
|
|
12
|
-
"startFrom": "src",
|
|
13
|
-
"backup": false,
|
|
14
|
-
"overwrite": true,
|
|
15
|
-
"generationStyle": "default-alias-named-star",
|
|
16
|
-
"output": "src",
|
|
17
|
-
"verbose": true
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
[36mvite v7.0.6 [32mbuilding SSR bundle for production...[36m[39m
|
|
2
|
-
- ctix 'create' mode start, ...
|
|
3
|
-
✔ /home/runner/work/utils/utils/packages/react/tsconfig.json loading complete!
|
|
4
|
-
✔ analysis export statements completed!
|
|
5
|
-
- build "index.ts" file start
|
|
6
|
-
- output file exists check, ...
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
✔ ctix 'create' mode complete!
|
|
10
|
-
transforming...
|
|
11
|
-
[32m✓[39m 8 modules transformed.
|
|
12
|
-
rendering chunks...
|
|
13
|
-
|
|
14
|
-
[vite:dts] Start generate declaration files...
|
|
15
|
-
[96msrc/index.ts[0m:[93m4[0m:[93m15[0m - [91merror[0m[90m TS2307: [0mCannot find module './useFetcher' or its corresponding type declarations.
|
|
16
|
-
|
|
17
|
-
[7m4[0m export * from './useFetcher';
|
|
18
|
-
[7m [0m [91m ~~~~~~~~~~~~~~[0m
|
|
19
|
-
|
|
20
|
-
[2mdist/[22m[36mindex.js [39m[1m[2m0.68 kB[22m[1m[22m[2m │ map: 0.10 kB[22m
|
|
21
|
-
[2mdist/[22m[36mapiCall.js [39m[1m[2m0.69 kB[22m[1m[22m[2m │ map: 1.45 kB[22m
|
|
22
|
-
[2mdist/[22m[36museFetchAction.js [39m[1m[2m0.79 kB[22m[1m[22m[2m │ map: 1.90 kB[22m
|
|
23
|
-
[2mdist/[22m[36mkeyboard.js [39m[1m[2m1.27 kB[22m[1m[22m[2m │ map: 2.81 kB[22m
|
|
24
|
-
[2mdist/[22m[36mform/client.js [39m[1m[2m1.58 kB[22m[1m[22m[2m │ map: 3.88 kB[22m
|
|
25
|
-
[2mdist/[22m[36museFetch.js [39m[1m[2m2.04 kB[22m[1m[22m[2m │ map: 5.62 kB[22m
|
|
26
|
-
[2mdist/[22m[36mform/form.js [39m[1m[2m2.89 kB[22m[1m[22m[2m │ map: 7.94 kB[22m
|
|
27
|
-
[vite:dts] Declaration files built in 2074ms.
|
|
28
|
-
|
|
29
|
-
[32m✓ built in 6.65s[39m
|
|
30
|
-
Updated package.json with exports
|
package/CHANGELOG.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# @kirill.konshin/react
|
|
2
|
-
|
|
3
|
-
## 0.0.4
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- useFetchAction
|
|
8
|
-
|
|
9
|
-
## 0.0.3
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- Minor fixes
|
|
14
|
-
|
|
15
|
-
## 0.0.2
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- 63fdba8: Agent-assisted refactoring
|
|
20
|
-
- 63fdba8: Divided core to browser/node/worker-specific packages, CTIX upgrade, etc.
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
|
package/src/apiCall.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export const jsonContentType = 'application/json';
|
|
2
|
-
|
|
3
|
-
export type DataResponse<R> = Response & { data?: R };
|
|
4
|
-
|
|
5
|
-
export async function apiCall<R>(url: string, init?: RequestInit): Promise<DataResponse<R>> {
|
|
6
|
-
const useBodyAsIs = !init?.body || init?.body instanceof FormData || typeof init?.body === 'string';
|
|
7
|
-
|
|
8
|
-
const res: DataResponse<R> = await fetch(url, {
|
|
9
|
-
method: 'POST',
|
|
10
|
-
...init,
|
|
11
|
-
body: useBodyAsIs ? init?.body : JSON.stringify(init.body),
|
|
12
|
-
headers: {
|
|
13
|
-
...init?.headers,
|
|
14
|
-
...(useBodyAsIs ? {} : { 'Content-Type': jsonContentType }),
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
if (res.headers.get('Content-Type')?.includes(jsonContentType)) {
|
|
19
|
-
res.data = await res.json();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
|
23
|
-
|
|
24
|
-
return res;
|
|
25
|
-
}
|
package/src/form/client.tsx
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useActionState, useCallback, useState, useTransition } from 'react';
|
|
4
|
-
import { create, MaybeTypeOf, Validation } from './form';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
const FORM_DEBUG = process.env.NEXT_PUBLIC_FORM_DEBUG === 'true';
|
|
8
|
-
|
|
9
|
-
export function createClient<S extends z.ZodObject<any>>(
|
|
10
|
-
schema: S,
|
|
11
|
-
): {
|
|
12
|
-
useValidation: (
|
|
13
|
-
actionFn: (data: FormData) => Promise<Validation<S>>,
|
|
14
|
-
initialData?: MaybeTypeOf<S>,
|
|
15
|
-
) => [state: Validation<S>, dispatch: (payload: FormData) => void, isPending: boolean];
|
|
16
|
-
useValidationTransition: (
|
|
17
|
-
actionFn: (data: FormData) => Promise<Validation<S>>,
|
|
18
|
-
initialData?: MaybeTypeOf<S>,
|
|
19
|
-
) => [Validation<S>, (formData: FormData) => Promise<Validation<S>>, boolean];
|
|
20
|
-
} {
|
|
21
|
-
const { validate } = create(schema);
|
|
22
|
-
|
|
23
|
-
function useValidationCallback(
|
|
24
|
-
actionFn: (data: FormData) => Promise<Validation<S>>,
|
|
25
|
-
): (formData: FormData) => Promise<Validation<S>> {
|
|
26
|
-
return useCallback(
|
|
27
|
-
async (formData: FormData) => {
|
|
28
|
-
const clientRes = validate(formData);
|
|
29
|
-
console.log('Client validation', FORM_DEBUG ? 'ignored' : 'active', clientRes);
|
|
30
|
-
if (!clientRes.success && !FORM_DEBUG) return clientRes;
|
|
31
|
-
|
|
32
|
-
const serverRes = await actionFn(formData);
|
|
33
|
-
console.log('Server validation', serverRes);
|
|
34
|
-
return serverRes;
|
|
35
|
-
},
|
|
36
|
-
[actionFn],
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function useValidation(
|
|
41
|
-
actionFn: (data: FormData) => Promise<Validation<S>>,
|
|
42
|
-
initialData: MaybeTypeOf<S> = {} as MaybeTypeOf<S>,
|
|
43
|
-
): [state: Validation<S>, dispatch: (payload: FormData) => void, isPending: boolean] {
|
|
44
|
-
const cb = useValidationCallback(actionFn);
|
|
45
|
-
|
|
46
|
-
return useActionState<Validation<S>, FormData>(async (_, data) => cb(data), {
|
|
47
|
-
success: false,
|
|
48
|
-
data: initialData,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function useValidationTransition(
|
|
53
|
-
actionFn: (data: FormData) => Promise<Validation<S>>,
|
|
54
|
-
initialData: MaybeTypeOf<S> = {} as MaybeTypeOf<S>,
|
|
55
|
-
): [Validation<S>, (formData: FormData) => Promise<Validation<S>>, boolean] {
|
|
56
|
-
const [isPending, startTransition] = useTransition();
|
|
57
|
-
const [state, setState] = useState<Validation<S>>({ success: false, data: initialData });
|
|
58
|
-
const cb = useValidationCallback(actionFn);
|
|
59
|
-
|
|
60
|
-
const wrappedCb = useCallback(
|
|
61
|
-
(formData: FormData) => {
|
|
62
|
-
const promise = cb(formData);
|
|
63
|
-
startTransition(() => promise.then(setState));
|
|
64
|
-
return promise;
|
|
65
|
-
},
|
|
66
|
-
[cb, startTransition],
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return [state, wrappedCb, isPending];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return { useValidation, useValidationTransition };
|
|
73
|
-
}
|