@kilpi/react-client
Version:
Kilpi React Client Components · Kilpi is the authorization framework for full-stack TypeScript applications, designed for flexible, powerful, agnostic, intuitive and developer friendly authorization.
98 lines (89 loc) • 2.7 kB
text/typescript
import type { KilpiClient } from "@kilpi/client";
import type { AnyKilpiCore } from "@kilpi/core";
import { useEffect, useState } from "react";
import { useCacheClearSignal } from "./useCacheClearSignal";
/**
* Value returned by useSubject
*/
type UseSubjectValue<T extends AnyKilpiCore> =
| {
status: "idle";
subject: null;
error: null;
}
| {
status: "loading";
subject: null;
error: null;
}
| {
status: "error";
subject: null;
error: unknown;
}
| {
status: "success";
subject: KilpiClient<T>["$$infer"]["subject"];
error: null;
};
/**
* Create the `useSubject` react client hook.
*/
export function createUseSubject<T extends AnyKilpiCore>(KilpiClient: KilpiClient<T>) {
/**
* React client hook for accessing the current subject.
*/
return function useSubject() {
/**
* Hold current state of useSubject.
*/
const [value, setValue] = useState<UseSubjectValue<T>>({
status: "idle",
subject: null,
error: null,
});
/**
* Reset state and force refetch whenever client cache is cleared.
*/
const cacheSignal = useCacheClearSignal(KilpiClient, () => {
setValue({ status: "idle", subject: null, error: null });
});
/**
* Fetch the current subject. It is OK to fire this fetch function in multiple components,
* as the subject is cached and only fetched once.
*/
useEffect(() => {
// Cleanup utilities: When unmounted, cancel fetch and prevent further state updates.
let isMounted = true;
const abortController = new AbortController();
// Define data fetching logic with respect to cleanup utilities.
async function fetchSubject() {
if (isMounted) setValue({ status: "loading", subject: null, error: null });
try {
const subject = await KilpiClient.fetchSubject({
queryOptions: { signal: abortController.signal },
});
if (isMounted) setValue({ status: "success", subject: subject, error: null });
} catch (error) {
if (isMounted) setValue({ status: "error", subject: null, error: error });
}
}
// Initiate fetch
fetchSubject();
// Trigger cleanup utilities
return () => {
isMounted = false;
abortController.abort();
};
}, [setValue, cacheSignal]);
/**
* Return value with computed values.
*/
return Object.assign(value, {
isLoading: value.status === "loading",
isError: value.status === "error",
isSuccess: value.status === "success",
isIdle: value.status === "idle",
});
};
}