@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
72 lines • 3.28 kB
JavaScript
import { hasCacheValue, isFailedCacheResult, isObsoleteCacheResult } from '@furystack/cache';
import { Shade, createComponent, transitionedValue } from '@furystack/shades';
import { cssVariableTheme } from '../services/css-variable-theme.js';
import { Button } from './button.js';
import { Result } from './result.js';
const getDefaultErrorUi = (error, retry) => (createComponent(Result, { status: "error", title: "Something went wrong", subtitle: String(error) },
createComponent(Button, { variant: "outlined", onclick: retry }, "Retry")));
/**
* Renders the state of a cache entry for the given `cache` + `args`.
*
* Subscribes to the cache observable and dispatches in this order:
* 1. **Error** — failed result renders the error UI with a retry callback.
* 2. **Value** — loaded or obsolete result renders `content`. Obsolete also
* triggers a single `cache.reload(...args)` per obsolete cycle.
* 3. **Loading** — no value, no error: renders `loader` (default `null`).
*
* @example
* ```tsx
* const MyContent = Shade<{ data: CacheWithValue<User> }>({
* customElementName: 'my-content',
* render: ({ props }) => <div>{props.data.value.name}</div>,
* })
*
* <CacheView cache={userCache} args={[userId]} content={MyContent} />
* ```
*/
export const CacheView = Shade({
customElementName: 'shade-cache-view',
css: {
fontFamily: cssVariableTheme.typography.fontFamily,
},
render: ({ props, useObservable, useState }) => {
const { cache, args, content, loader, error, contentProps, viewTransition } = props;
const argsKey = JSON.stringify(args);
const observable = cache.getObservable(...args);
const [result] = useObservable(`cache-${argsKey}`, observable);
const getCategory = (r) => isFailedCacheResult(r) ? 'error' : hasCacheValue(r) ? 'value' : 'loading';
const displayedResult = transitionedValue(useState, 'displayedResult', result, viewTransition, (prev, next) => getCategory(prev) !== getCategory(next));
const [lastReloadedArgsKey, setLastReloadedArgsKey] = useState('lastReloadedArgsKey', null);
const retry = () => {
cache.reload(...args).catch(() => {
/* error state will be set by cache */
});
};
// 1. Error first
if (isFailedCacheResult(displayedResult)) {
const errorRenderer = error ?? getDefaultErrorUi;
return errorRenderer(displayedResult.error, retry);
}
// 2. Value next
if (hasCacheValue(displayedResult)) {
if (isObsoleteCacheResult(displayedResult)) {
if (lastReloadedArgsKey !== argsKey) {
setLastReloadedArgsKey(argsKey);
cache.reload(...args).catch(() => {
/* error state will be set by cache */
});
}
}
else if (lastReloadedArgsKey !== null) {
setLastReloadedArgsKey(null);
}
return createComponent(content, {
data: displayedResult,
...(contentProps ?? {}),
});
}
// 3. Loading last
return loader ?? null;
},
});
//# sourceMappingURL=cache-view.js.map