@alepha/react 0.10.6 → 0.11.0

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.
@@ -4,51 +4,51 @@ import { useRouter } from "./useRouter.ts";
4
4
  import { useRouterState } from "./useRouterState.ts";
5
5
 
6
6
  export interface UseActiveOptions {
7
- href: string;
8
- startWith?: boolean;
7
+ href: string;
8
+ startWith?: boolean;
9
9
  }
10
10
 
11
11
  export const useActive = (args: string | UseActiveOptions): UseActiveHook => {
12
- const router = useRouter();
13
- const [isPending, setPending] = useState(false);
14
- const state = useRouterState();
15
- const current = state.url.pathname;
16
-
17
- const options: UseActiveOptions =
18
- typeof args === "string" ? { href: args } : { ...args, href: args.href };
19
- const href = options.href;
20
-
21
- let isActive =
22
- current === href || current === `${href}/` || `${current}/` === href;
23
-
24
- if (options.startWith && !isActive) {
25
- isActive = current.startsWith(href);
26
- }
27
-
28
- return {
29
- isPending,
30
- isActive,
31
- anchorProps: {
32
- href: router.base(href),
33
- onClick: async (ev?: any) => {
34
- ev?.stopPropagation();
35
- ev?.preventDefault();
36
- if (isActive) return;
37
- if (isPending) return;
38
-
39
- setPending(true);
40
- try {
41
- await router.go(href);
42
- } finally {
43
- setPending(false);
44
- }
45
- },
46
- },
47
- };
12
+ const router = useRouter();
13
+ const [isPending, setPending] = useState(false);
14
+ const state = useRouterState();
15
+ const current = state.url.pathname;
16
+
17
+ const options: UseActiveOptions =
18
+ typeof args === "string" ? { href: args } : { ...args, href: args.href };
19
+ const href = options.href;
20
+
21
+ let isActive =
22
+ current === href || current === `${href}/` || `${current}/` === href;
23
+
24
+ if (options.startWith && !isActive) {
25
+ isActive = current.startsWith(href);
26
+ }
27
+
28
+ return {
29
+ isPending,
30
+ isActive,
31
+ anchorProps: {
32
+ href: router.base(href),
33
+ onClick: async (ev?: any) => {
34
+ ev?.stopPropagation();
35
+ ev?.preventDefault();
36
+ if (isActive) return;
37
+ if (isPending) return;
38
+
39
+ setPending(true);
40
+ try {
41
+ await router.go(href);
42
+ } finally {
43
+ setPending(false);
44
+ }
45
+ },
46
+ },
47
+ };
48
48
  };
49
49
 
50
50
  export interface UseActiveHook {
51
- isActive: boolean;
52
- anchorProps: AnchorProps;
53
- isPending: boolean;
51
+ isActive: boolean;
52
+ anchorProps: AnchorProps;
53
+ isPending: boolean;
54
54
  }
@@ -15,12 +15,12 @@ import { AlephaContext } from "../contexts/AlephaContext.ts";
15
15
  * etc...
16
16
  */
17
17
  export const useAlepha = (): Alepha => {
18
- const alepha = useContext(AlephaContext);
19
- if (!alepha) {
20
- throw new AlephaError(
21
- "Hook 'useAlepha()' must be used within an AlephaContext.Provider",
22
- );
23
- }
18
+ const alepha = useContext(AlephaContext);
19
+ if (!alepha) {
20
+ throw new AlephaError(
21
+ "Hook 'useAlepha()' must be used within an AlephaContext.Provider",
22
+ );
23
+ }
24
24
 
25
- return alepha;
25
+ return alepha;
26
26
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
- type ClientScope,
3
- type HttpVirtualClient,
4
- LinkProvider,
2
+ type ClientScope,
3
+ type HttpVirtualClient,
4
+ LinkProvider,
5
5
  } from "@alepha/server-links";
6
6
  import { useInject } from "./useInject.ts";
7
7
 
@@ -11,7 +11,7 @@ import { useInject } from "./useInject.ts";
11
11
  * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
12
12
  */
13
13
  export const useClient = <T extends object>(
14
- scope?: ClientScope,
14
+ scope?: ClientScope,
15
15
  ): HttpVirtualClient<T> => {
16
- return useInject(LinkProvider).client<T>(scope);
16
+ return useInject(LinkProvider).client<T>(scope);
17
17
  };
@@ -7,6 +7,6 @@ import { useAlepha } from "./useAlepha.ts";
7
7
  * It's a wrapper of `useAlepha().inject(service)` with a memoization.
8
8
  */
9
9
  export const useInject = <T extends object>(service: Service<T>): T => {
10
- const alepha = useAlepha();
11
- return useMemo(() => alepha.inject(service), []);
10
+ const alepha = useAlepha();
11
+ return useMemo(() => alepha.inject(service), []);
12
12
  };
@@ -7,54 +7,57 @@ import { useRouter } from "./useRouter.ts";
7
7
  * Not well tested. Use with caution.
8
8
  */
9
9
  export const useQueryParams = <T extends TObject>(
10
- schema: T,
11
- options: UseQueryParamsHookOptions = {},
10
+ schema: T,
11
+ options: UseQueryParamsHookOptions = {},
12
12
  ): [Partial<Static<T>>, (data: Static<T>) => void] => {
13
- const alepha = useAlepha();
14
-
15
- const key = options.key ?? "q";
16
- const router = useRouter();
17
- const querystring = router.query[key];
18
-
19
- const [queryParams = {}, setQueryParams] = useState<Static<T> | undefined>(
20
- decode(alepha, schema, router.query[key]),
21
- );
22
-
23
- useEffect(() => {
24
- setQueryParams(decode(alepha, schema, querystring));
25
- }, [querystring]);
26
-
27
- return [
28
- queryParams,
29
- (queryParams: Static<T>) => {
30
- setQueryParams(queryParams);
31
- router.setQueryParams((data) => {
32
- return { ...data, [key]: encode(alepha, schema, queryParams) };
33
- });
34
- },
35
- ];
13
+ const alepha = useAlepha();
14
+
15
+ const key = options.key ?? "q";
16
+ const router = useRouter();
17
+ const querystring = router.query[key];
18
+
19
+ const [queryParams = {}, setQueryParams] = useState<Static<T> | undefined>(
20
+ decode(alepha, schema, router.query[key]),
21
+ );
22
+
23
+ useEffect(() => {
24
+ setQueryParams(decode(alepha, schema, querystring));
25
+ }, [querystring]);
26
+
27
+ return [
28
+ queryParams,
29
+ (queryParams: Static<T>) => {
30
+ setQueryParams(queryParams);
31
+ router.setQueryParams((data) => {
32
+ return { ...data, [key]: encode(alepha, schema, queryParams) };
33
+ });
34
+ },
35
+ ];
36
36
  };
37
37
 
38
38
  // ---------------------------------------------------------------------------------------------------------------------
39
39
 
40
40
  export interface UseQueryParamsHookOptions {
41
- format?: "base64" | "querystring";
42
- key?: string;
43
- push?: boolean;
41
+ format?: "base64" | "querystring";
42
+ key?: string;
43
+ push?: boolean;
44
44
  }
45
45
 
46
46
  const encode = (alepha: Alepha, schema: TObject, data: any) => {
47
- return btoa(JSON.stringify(alepha.parse(schema, data)));
47
+ return btoa(JSON.stringify(alepha.codec.decode(schema, data)));
48
48
  };
49
49
 
50
50
  const decode = <T extends TObject>(
51
- alepha: Alepha,
52
- schema: T,
53
- data: any,
51
+ alepha: Alepha,
52
+ schema: T,
53
+ data: any,
54
54
  ): Static<T> | undefined => {
55
- try {
56
- return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
57
- } catch {
58
- return;
59
- }
55
+ try {
56
+ return alepha.codec.decode(
57
+ schema,
58
+ JSON.parse(atob(decodeURIComponent(data))),
59
+ );
60
+ } catch {
61
+ return;
62
+ }
60
63
  };
@@ -16,5 +16,5 @@ import { useInject } from "./useInject.ts";
16
16
  * router.go("home"); // typesafe
17
17
  */
18
18
  export const useRouter = <T extends object = any>(): ReactRouter<T> => {
19
- return useInject(ReactRouter<T>);
19
+ return useInject(ReactRouter<T>);
20
20
  };
@@ -3,64 +3,64 @@ import { useEffect } from "react";
3
3
  import { useAlepha } from "./useAlepha.ts";
4
4
 
5
5
  type Hook<T extends keyof Hooks> =
6
- | ((ev: Hooks[T]) => void)
7
- | {
8
- priority?: "first" | "last";
9
- callback: (ev: Hooks[T]) => void;
10
- };
6
+ | ((ev: Hooks[T]) => void)
7
+ | {
8
+ priority?: "first" | "last";
9
+ callback: (ev: Hooks[T]) => void;
10
+ };
11
11
 
12
12
  /**
13
13
  * Subscribe to various router events.
14
14
  */
15
15
  export const useRouterEvents = (
16
- opts: {
17
- onBegin?: Hook<"react:transition:begin">;
18
- onError?: Hook<"react:transition:error">;
19
- onEnd?: Hook<"react:transition:end">;
20
- onSuccess?: Hook<"react:transition:success">;
21
- } = {},
22
- deps: any[] = [],
16
+ opts: {
17
+ onBegin?: Hook<"react:transition:begin">;
18
+ onError?: Hook<"react:transition:error">;
19
+ onEnd?: Hook<"react:transition:end">;
20
+ onSuccess?: Hook<"react:transition:success">;
21
+ } = {},
22
+ deps: any[] = [],
23
23
  ) => {
24
- const alepha = useAlepha();
24
+ const alepha = useAlepha();
25
25
 
26
- useEffect(() => {
27
- if (!alepha.isBrowser()) {
28
- return;
29
- }
26
+ useEffect(() => {
27
+ if (!alepha.isBrowser()) {
28
+ return;
29
+ }
30
30
 
31
- const cb = <T extends keyof Hooks>(callback: Hook<T>) => {
32
- if (typeof callback === "function") {
33
- return { callback };
34
- }
35
- return callback;
36
- };
31
+ const cb = <T extends keyof Hooks>(callback: Hook<T>) => {
32
+ if (typeof callback === "function") {
33
+ return { callback };
34
+ }
35
+ return callback;
36
+ };
37
37
 
38
- const subs: Function[] = [];
39
- const onBegin = opts.onBegin;
40
- const onEnd = opts.onEnd;
41
- const onError = opts.onError;
42
- const onSuccess = opts.onSuccess;
38
+ const subs: Function[] = [];
39
+ const onBegin = opts.onBegin;
40
+ const onEnd = opts.onEnd;
41
+ const onError = opts.onError;
42
+ const onSuccess = opts.onSuccess;
43
43
 
44
- if (onBegin) {
45
- subs.push(alepha.events.on("react:transition:begin", cb(onBegin)));
46
- }
44
+ if (onBegin) {
45
+ subs.push(alepha.events.on("react:transition:begin", cb(onBegin)));
46
+ }
47
47
 
48
- if (onEnd) {
49
- subs.push(alepha.events.on("react:transition:end", cb(onEnd)));
50
- }
48
+ if (onEnd) {
49
+ subs.push(alepha.events.on("react:transition:end", cb(onEnd)));
50
+ }
51
51
 
52
- if (onError) {
53
- subs.push(alepha.events.on("react:transition:error", cb(onError)));
54
- }
52
+ if (onError) {
53
+ subs.push(alepha.events.on("react:transition:error", cb(onError)));
54
+ }
55
55
 
56
- if (onSuccess) {
57
- subs.push(alepha.events.on("react:transition:success", cb(onSuccess)));
58
- }
56
+ if (onSuccess) {
57
+ subs.push(alepha.events.on("react:transition:success", cb(onSuccess)));
58
+ }
59
59
 
60
- return () => {
61
- for (const sub of subs) {
62
- sub();
63
- }
64
- };
65
- }, deps);
60
+ return () => {
61
+ for (const sub of subs) {
62
+ sub();
63
+ }
64
+ };
65
+ }, deps);
66
66
  };
@@ -3,9 +3,9 @@ import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
3
3
  import { useStore } from "./useStore.ts";
4
4
 
5
5
  export const useRouterState = (): ReactRouterState => {
6
- const [state] = useStore("react.router.state");
7
- if (!state) {
8
- throw new AlephaError("Missing react router state");
9
- }
10
- return state;
6
+ const [state] = useStore("react.router.state");
7
+ if (!state) {
8
+ throw new AlephaError("Missing react router state");
9
+ }
10
+ return state;
11
11
  };
@@ -1,8 +1,8 @@
1
1
  import type { Alepha } from "@alepha/core";
2
2
  import {
3
- type FetchOptions,
4
- HttpClient,
5
- type RequestConfigSchema,
3
+ type FetchOptions,
4
+ HttpClient,
5
+ type RequestConfigSchema,
6
6
  } from "@alepha/server";
7
7
  import { LinkProvider, type VirtualAction } from "@alepha/server-links";
8
8
  import { useEffect, useState } from "react";
@@ -10,34 +10,34 @@ import { useAlepha } from "./useAlepha.ts";
10
10
  import { useInject } from "./useInject.ts";
11
11
 
12
12
  export const useSchema = <TConfig extends RequestConfigSchema>(
13
- action: VirtualAction<TConfig>,
13
+ action: VirtualAction<TConfig>,
14
14
  ): UseSchemaReturn<TConfig> => {
15
- const name = action.name;
16
- const alepha = useAlepha();
17
- const httpClient = useInject(HttpClient);
18
- const [schema, setSchema] = useState<UseSchemaReturn<TConfig>>(
19
- ssrSchemaLoading(alepha, name) as UseSchemaReturn<TConfig>,
20
- );
15
+ const name = action.name;
16
+ const alepha = useAlepha();
17
+ const httpClient = useInject(HttpClient);
18
+ const [schema, setSchema] = useState<UseSchemaReturn<TConfig>>(
19
+ ssrSchemaLoading(alepha, name) as UseSchemaReturn<TConfig>,
20
+ );
21
21
 
22
- useEffect(() => {
23
- if (!schema.loading) {
24
- return;
25
- }
22
+ useEffect(() => {
23
+ if (!schema.loading) {
24
+ return;
25
+ }
26
26
 
27
- const opts: FetchOptions = {
28
- cache: true,
29
- };
27
+ const opts: FetchOptions = {
28
+ localCache: true,
29
+ };
30
30
 
31
- httpClient
32
- .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts)
33
- .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));
34
- }, [name]);
31
+ httpClient
32
+ .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, opts)
33
+ .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));
34
+ }, [name]);
35
35
 
36
- return schema;
36
+ return schema;
37
37
  };
38
38
 
39
39
  export type UseSchemaReturn<TConfig extends RequestConfigSchema> = TConfig & {
40
- loading: boolean;
40
+ loading: boolean;
41
41
  };
42
42
 
43
43
  // ---------------------------------------------------------------------------------------------------------------------
@@ -46,43 +46,43 @@ export type UseSchemaReturn<TConfig extends RequestConfigSchema> = TConfig & {
46
46
  * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
47
47
  */
48
48
  export const ssrSchemaLoading = (alepha: Alepha, name: string) => {
49
- // server-side rendering (SSR) context
50
- if (!alepha.isBrowser()) {
51
- // get user links
52
- const linkProvider = alepha.inject(LinkProvider);
49
+ // server-side rendering (SSR) context
50
+ if (!alepha.isBrowser()) {
51
+ // get user links
52
+ const linkProvider = alepha.inject(LinkProvider);
53
53
 
54
- // check if user can access the link
55
- const can = linkProvider
56
- .getServerLinks()
57
- .find((link) => link.name === name);
54
+ // check if user can access the link
55
+ const can = linkProvider
56
+ .getServerLinks()
57
+ .find((link) => link.name === name);
58
58
 
59
- // yes!
60
- if (can) {
61
- // user-links have no schema, so we need to get it from the provider
62
- const schema = linkProvider.links.find((it) => it.name === name)?.schema;
59
+ // yes!
60
+ if (can) {
61
+ // user-links have no schema, so we need to get it from the provider
62
+ const schema = linkProvider.links.find((it) => it.name === name)?.schema;
63
63
 
64
- // oh, we have a schema!
65
- if (schema) {
66
- // attach to user link, it will be used in the client during hydration
67
- can.schema = schema;
68
- return schema;
69
- }
70
- }
64
+ // oh, we have a schema!
65
+ if (schema) {
66
+ // attach to user link, it will be used in the client during hydration
67
+ can.schema = schema;
68
+ return schema;
69
+ }
70
+ }
71
71
 
72
- return { loading: true };
73
- }
72
+ return { loading: true };
73
+ }
74
74
 
75
- // browser side rendering (CSR) context
76
- // check if we have the schema already loaded
77
- const schema = alepha
78
- .inject(LinkProvider)
79
- .links.find((it) => it.name === name)?.schema;
75
+ // browser side rendering (CSR) context
76
+ // check if we have the schema already loaded
77
+ const schema = alepha
78
+ .inject(LinkProvider)
79
+ .links.find((it) => it.name === name)?.schema;
80
80
 
81
- // yes!
82
- if (schema) {
83
- return schema;
84
- }
81
+ // yes!
82
+ if (schema) {
83
+ return schema;
84
+ }
85
85
 
86
- // no, we need to load it
87
- return { loading: true };
86
+ // no, we need to load it
87
+ return { loading: true };
88
88
  };
@@ -6,35 +6,35 @@ import { useAlepha } from "./useAlepha.ts";
6
6
  * Hook to access and mutate the Alepha state.
7
7
  */
8
8
  export const useStore = <Key extends keyof State>(
9
- key: Key,
10
- defaultValue?: State[Key],
9
+ key: Key,
10
+ defaultValue?: State[Key],
11
11
  ): [State[Key], (value: State[Key]) => void] => {
12
- const alepha = useAlepha();
12
+ const alepha = useAlepha();
13
13
 
14
- useMemo(() => {
15
- if (defaultValue != null && alepha.state.get(key) == null) {
16
- alepha.state.set(key, defaultValue);
17
- }
18
- }, [defaultValue]);
14
+ useMemo(() => {
15
+ if (defaultValue != null && alepha.state.get(key) == null) {
16
+ alepha.state.set(key, defaultValue);
17
+ }
18
+ }, [defaultValue]);
19
19
 
20
- const [state, setState] = useState(alepha.state.get(key));
20
+ const [state, setState] = useState(alepha.state.get(key));
21
21
 
22
- useEffect(() => {
23
- if (!alepha.isBrowser()) {
24
- return;
25
- }
22
+ useEffect(() => {
23
+ if (!alepha.isBrowser()) {
24
+ return;
25
+ }
26
26
 
27
- return alepha.events.on("state:mutate", (ev) => {
28
- if (ev.key === key) {
29
- setState(ev.value);
30
- }
31
- });
32
- }, []);
27
+ return alepha.events.on("state:mutate", (ev) => {
28
+ if (ev.key === key) {
29
+ setState(ev.value);
30
+ }
31
+ });
32
+ }, []);
33
33
 
34
- return [
35
- state,
36
- (value: State[Key]) => {
37
- alepha.state.set(key, value);
38
- },
39
- ] as const;
34
+ return [
35
+ state,
36
+ (value: State[Key]) => {
37
+ alepha.state.set(key, value);
38
+ },
39
+ ] as const;
40
40
  };
@@ -18,22 +18,22 @@ export * from "./providers/ReactPageProvider.ts";
18
18
  // ---------------------------------------------------------------------------------------------------------------------
19
19
 
20
20
  export const AlephaReact = $module({
21
- name: "alepha.react",
22
- descriptors: [$page],
23
- services: [
24
- ReactPageProvider,
25
- ReactBrowserRouterProvider,
26
- ReactBrowserProvider,
27
- ReactRouter,
28
- ReactBrowserRendererProvider,
29
- ],
30
- register: (alepha) =>
31
- alepha
32
- .with(AlephaServer)
33
- .with(AlephaServerLinks)
34
- .with(ReactPageProvider)
35
- .with(ReactBrowserProvider)
36
- .with(ReactBrowserRouterProvider)
37
- .with(ReactBrowserRendererProvider)
38
- .with(ReactRouter),
21
+ name: "alepha.react",
22
+ descriptors: [$page],
23
+ services: [
24
+ ReactPageProvider,
25
+ ReactBrowserRouterProvider,
26
+ ReactBrowserProvider,
27
+ ReactRouter,
28
+ ReactBrowserRendererProvider,
29
+ ],
30
+ register: (alepha) =>
31
+ alepha
32
+ .with(AlephaServer)
33
+ .with(AlephaServerLinks)
34
+ .with(ReactPageProvider)
35
+ .with(ReactBrowserProvider)
36
+ .with(ReactBrowserRouterProvider)
37
+ .with(ReactBrowserRendererProvider)
38
+ .with(ReactRouter),
39
39
  });