Remove dependency to evt in the component library

This commit is contained in:
Joseph Garrone
2024-05-27 00:12:51 +02:00
parent a3270d10f0
commit 338642094d
8 changed files with 132 additions and 8 deletions

View File

@ -65,7 +65,6 @@
"react": "*" "react": "*"
}, },
"dependencies": { "dependencies": {
"evt": "^2.5.7",
"minimal-polyfills": "^2.2.3", "minimal-polyfills": "^2.2.3",
"react-markdown": "^5.0.3", "react-markdown": "^5.0.3",
"tsafe": "^1.6.6" "tsafe": "^1.6.6"
@ -121,6 +120,7 @@
"vite": "^5.2.11", "vite": "^5.2.11",
"vitest": "^0.29.8", "vitest": "^0.29.8",
"yauzl": "^2.10.0", "yauzl": "^2.10.0",
"zod": "^3.17.10" "zod": "^3.17.10",
"evt": "^2.5.7"
} }
} }

View File

@ -4,11 +4,13 @@ import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
import { useConst } from "keycloakify/tools/useConst"; import { useConst } from "keycloakify/tools/useConst";
import { useConstCallback } from "keycloakify/tools/useConstCallback"; import { useConstCallback } from "keycloakify/tools/useConstCallback";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Evt } from "evt"; import {
import { useRerenderOnStateChange } from "evt/hooks/useRerenderOnStateChange"; createStatefulObservable,
useRerenderOnChange
} from "keycloakify/tools/StatefulObservable";
import { KcContext } from "../kcContext"; import { KcContext } from "../kcContext";
const evtTermsMarkdown = Evt.create<string | undefined>(undefined); const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
export type KcContextLike = { export type KcContextLike = {
pageId: string; pageId: string;
@ -45,15 +47,15 @@ export function useDownloadTerms(params: {
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) { if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
downloadTermMarkdownMemoized( downloadTermMarkdownMemoized(
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
).then(thermMarkdown => (evtTermsMarkdown.state = thermMarkdown)); ).then(thermMarkdown => (obsTermsMarkdown.current = thermMarkdown));
} }
}, []); }, []);
} }
export function useTermsMarkdown() { export function useTermsMarkdown() {
useRerenderOnStateChange(evtTermsMarkdown); useRerenderOnChange(obsTermsMarkdown);
const termsMarkdown = evtTermsMarkdown.state; const termsMarkdown = obsTermsMarkdown.current;
return { termsMarkdown }; return { termsMarkdown };
} }

View File

@ -0,0 +1,16 @@
`StatefulObservable` is a construct that allow to avoid having to depend on [EVT](https://evt.land).
A `StatefulObservable` can be converted to an evt with:
```ts
import { statefulObservableToStatefulEvt } from "powerhooks/tools/StatefulObservable/statefulObservableToStatefulEvt";
const evtXyz = statefulObservableToStatefulEvt({
statefulObservable: $xyz
//Optionally you can pass a Ctx
});
```
WARNING: Unlike `StatefulEvt`, `StatefulObservable` do not post when we first attach.
If the current value was not yet evaluated `next()` is called on the initial value returned by the function that
returns it.

View File

@ -0,0 +1,58 @@
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
export type StatefulObservable<T> = {
current: T;
subscribe: (next: (data: T) => void) => Subscription;
};
export type Subscription = {
unsubscribe(): void;
};
export function createStatefulObservable<T>(
getInitialValue: () => T
): StatefulObservable<T> {
const nextFunctions: ((data: T) => void)[] = [];
const { get, set } = (() => {
let wrappedState: [T] | undefined = undefined;
function set(data: T) {
wrappedState = [data];
nextFunctions.forEach(next => next(data));
}
return {
get: () => {
if (wrappedState === undefined) {
set(getInitialValue());
assert(!is<undefined>(wrappedState));
}
return wrappedState[0];
},
set
};
})();
return Object.defineProperty(
{
current: null as any as T,
subscribe: (next: (data: T) => void) => {
nextFunctions.push(next);
return {
unsubscribe: () =>
nextFunctions.splice(nextFunctions.indexOf(next), 1)
};
}
},
"current",
{
enumerable: true,
get,
set
}
);
}

View File

@ -0,0 +1,2 @@
export * from "./useObservable";
export * from "./useRerenderOnChange";

View File

@ -0,0 +1,25 @@
import { useEffect } from "react";
import type { Subscription } from "../StatefulObservable";
/**
* Equivalent of https://docs.evt.land/api/react-hooks
*/
export function useObservable(
effect: (params: {
registerSubscription: (subscription: Subscription) => void;
}) => void,
deps: React.DependencyList
): void {
useEffect(() => {
const subscriptions: Subscription[] = [];
effect({
registerSubscription: subscription => subscriptions.push(subscription)
});
return () => {
subscriptions.forEach(subscription => subscription.unsubscribe());
subscriptions.length = 0;
};
}, deps);
}

View File

@ -0,0 +1,19 @@
import { useObservable } from "./useObservable";
import { useState } from "react";
import type { StatefulObservable } from "../StatefulObservable";
/**
* Equivalent of https://docs.evt.land/api/react-hooks
* */
export function useRerenderOnChange($: StatefulObservable<unknown>): void {
//NOTE: We use function in case the state is a function
const [, setCurrent] = useState(() => $.current);
useObservable(
({ registerSubscription }) => {
const subscription = $.subscribe(current => setCurrent(() => current));
registerSubscription(subscription);
},
[$]
);
}

View File

@ -0,0 +1,2 @@
export * from "./StatefulObservable";
export * from "./hooks";