sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
78 lines (69 loc) • 2.88 kB
text/typescript
import {useMemoObservable} from 'react-rx'
import {concat, type Observable, of} from 'rxjs'
import {catchError, distinctUntilChanged, map, scan, switchMap} from 'rxjs/operators'
import {useUnique} from './useUnique'
/** @internal */
export type LoadingTuple<T> = [T, boolean]
/** @internal */
export type ReactHook<TArgs, TResult> = (args: TArgs) => TResult
/** @internal */
// overloads to handle types where an initial value is passed
export function createHookFromObservableFactory<T, TArg = void>(
observableFactory: (arg: TArg) => Observable<T>,
initialValue: T,
): ReactHook<TArg, LoadingTuple<T>>
/** @internal */
export function createHookFromObservableFactory<T, TArg = void>(
observableFactory: (arg: TArg) => Observable<T>,
initialValue?: T,
): ReactHook<TArg, LoadingTuple<T | undefined>>
/**
* A function that will create a hook from a function that returns an
* observable. The parameters of the hook will be the parameters of the function
* and the return of the hook will be a loading tuple with the value of the
* observable at the first index and a boolean with the loading state as the
* second index.
*
* The loading state will become true as soon as new incoming args are given and
* will flip to false when the observable from the function emits the next
* value.
*
* @internal
*/
export function createHookFromObservableFactory<T, TArg = void>(
observableFactory: (arg: TArg) => Observable<T>,
initialValue?: T,
): ReactHook<TArg, LoadingTuple<T | undefined>> {
const initialLoadingTuple: LoadingTuple<T | undefined> = [initialValue, true]
const initialResult = {type: 'tuple', tuple: initialLoadingTuple} as const
return function useLoadableFromCreateLoadable(_arg: TArg) {
// @todo refactor callsites to make use of useMemo so that this hook can be removed
const memoArg = useUnique(_arg)
const result = useMemoObservable(
() =>
of(memoArg).pipe(
switchMap((arg) =>
concat(
of({type: 'loading'} as const),
observableFactory(arg).pipe(map((value) => ({type: 'value', value}) as const)),
),
),
scan(([prevValue], next): LoadingTuple<T | undefined> => {
if (next.type === 'loading') return [prevValue, true]
return [next.value, false]
}, initialLoadingTuple),
distinctUntilChanged(([prevValue, prevIsLoading], [nextValue, nextIsLoading]) => {
if (prevValue !== nextValue) return false
if (prevIsLoading !== nextIsLoading) return false
return true
}),
map((tuple) => ({type: 'tuple', tuple}) as const),
catchError((error) => of({type: 'error', error} as const)),
),
[memoArg],
initialResult,
)
if (result.type === 'error') throw result.error
return result.tuple
}
}