UNPKG

@typed/fp

Version:

Data Structures and Resources for fp-ts

165 lines 6.52 kB
/** * @typed/fp/Use is the only non-referentially transparent module in @typed/fp. It is built atop * of [Ref](./Ref.ts.md) to enable many common workflows. If you're coming from a React background, it is * pretty similar to hooks, but the only constraint is that is should be declared once at the top of the scope of your module. * @since 0.11.0 */ import { disposeBoth, disposeNone } from '@most/disposable'; import { flow, pipe } from 'fp-ts/function'; import * as O from 'fp-ts/Option'; import { not } from 'fp-ts/Predicate'; import * as E from './Env'; import * as EO from './EnvOption'; import { alwaysEqualsEq, deepEqualsEq, EqStrict } from './Eq'; import * as KV from './KV'; import * as RS from './ReaderStream'; import * as Ref from './Ref'; import * as RefDisposable from './RefDisposable'; import * as R from './Resume'; import { delay } from './Scheduler'; import * as S from './Stream'; /** * Use Refs to check if a value has changed between invocations * @since 0.11.0 * @category Constructor */ export const defaultOptionRef = () => Ref.kv(E.of(O.none), alwaysEqualsEq); /** * Use Refs to check if a value has changed between invocations * @since 0.11.0 * @category Combinator */ export function useEqWith(ref) { return (Eq = deepEqualsEq, initial = true) => (value) => pipe(E.Do, E.bindW('previous', () => ref.get), E.bindW('changed', ({ previous }) => pipe(previous, O.matchW(() => initial, not(Eq.equals(value))), E.of)), E.chainW(({ previous, changed }) => pipe(previous, O.matchW(() => E.of(changed), () => pipe(value, O.some, ref.set, E.constant(changed)))))); } /** * Use Refs to check if a value has changed between invocations * @since 0.11.0 * @category Combinator */ export const useEq = (Eq = deepEqualsEq, initial = true) => useEqWith(defaultOptionRef())(Eq, initial); /** * @since 0.11.0 * @category Use */ export const useMemoWith = (options) => (env, Eq = deepEqualsEq) => { const changed = pipe(Eq, useEqWith(options.changed)); const updateRef = options.currentValue.update(() => EO.fromEnv(env)); return flow(changed, E.chainFirstW((changed) => (changed ? updateRef : E.of(null))), E.chainW(() => options.currentValue.get), EO.getOrElseEW(() => pipe(env, E.chainFirstW(flow(O.some, options.currentValue.set))))); }; const defaultUseMemoRefs = () => ({ currentValue: defaultOptionRef(), changed: defaultOptionRef(), }); /** * @since 0.11.0 * @category Use */ export const useMemo = (env, Eq = deepEqualsEq) => useMemoWith(defaultUseMemoRefs())(env, Eq); /** * @since 0.11.0 * @category Use */ export const useDisposableWith = (options) => (Eq = deepEqualsEq, switchLatest = false) => { const changed = useEqWith(options.changed)(Eq); return (f, value) => pipe(E.Do, E.bindW('changed', () => changed(value)), E.bindW('current', () => options.disposable.get), E.chainW(({ changed, current }) => changed ? pipe(E.fromIO(() => (switchLatest ? current.dispose() : null)), E.chainW(() => E.fromIO(f)), E.chainW((next) => pipe(next, RefDisposable.add, E.map((d) => disposeBoth(d, next)), E.chainW((a) => options.disposable.set(a))))) : E.of(current))); }; const defaultDisposableRefs = () => ({ disposable: Ref.kv(E.fromIO(disposeNone)), changed: defaultOptionRef(), }); /** * @since 0.11.0 * @category Use */ export const useDisposable = (Eq = deepEqualsEq, switchLatest = false) => useDisposableWith(defaultDisposableRefs())(Eq, switchLatest); /** * @since 0.11.0 * @category Use */ export const useEffectWith = (options) => { const useD = useDisposableWith(options); return (Eq = deepEqualsEq, switchLatest = false) => { const use = useD(Eq, switchLatest); return (env, value) => pipe(E.ask(), E.chainW((r) => use(() => pipe(r, pipe(delay(0), E.chainW(() => env)), R.exec), value))); }; }; /** * @since 0.11.0 * @category Use */ export const useWithPrevious = (ref) => { return (f, value) => pipe(ref.get, E.map((previous) => f(previous, value)), E.chainFirstW(() => pipe(value, O.some, ref.set))); }; /** * Helps you to convert a Kliesli arrow of an Env into a function to * a Disposable. Useful for UIs where you need to provide onClick={fn} * style handlers. * @since 0.11.0 * @category Use */ export function useEnvK(f, onValue = E.of) { return pipe(E.Do, E.apSW('refDisposable', RefDisposable.get), E.apSW('resumeF', E.toResumeK(f)), E.apSW('resumeV', E.toResumeK(onValue)), E.map(({ resumeF, resumeV, refDisposable }) => (...args) => { const d1 = pipe(resumeF(...args), R.chain(resumeV), R.exec); const d2 = refDisposable.addDisposable(d1); return disposeBoth(d1, d2); })); } /** * @since 0.11.0 * @category Use */ export const bindEnvK = (name, f, onValue) => (ma) => E.bindW(name, () => useEnvK(f, onValue))(ma); /** * @since 0.11.0 * @category Use */ export const useReaderStreamWith = (options) => (Eq = deepEqualsEq) => { const use = useDisposableWith(options)(Eq); return (rs, dep) => pipe(E.asksE((r) => use(() => rs(r).run(S.createSink({ event: (_, value) => pipe(value, O.some, options.value.set, E.execWith(r)), }), r.scheduler), dep)), E.chainW(() => options.value.get)); }; const defaultUserReaderStreamRefs = () => ({ disposable: Ref.kv(E.fromIO(disposeNone)), changed: defaultOptionRef(), value: defaultOptionRef(), }); /** * @since 0.11.0 * @category Use */ export const useReaderStream = (Eq = deepEqualsEq) => useReaderStreamWith(defaultUserReaderStreamRefs())(Eq); /** * @since 0.11.0 * @category Use */ export const useStreamWith = (options) => (Eq = deepEqualsEq) => { const useRS = pipe(Eq, useReaderStreamWith(options)); return (stream, dep) => useRS(() => stream, dep); }; /** * @since 0.11.0 * @category Use */ export const useStream = (Eq = deepEqualsEq) => { const use = useStreamWith(defaultUserReaderStreamRefs())(Eq); return (stream, dep) => use(stream, dep); }; /** * @since 0.11.0 * @category Use */ export const useKVStream = (f, Eq) => { const use = RS.fromEnv(KV.useKeyedEnvs(EqStrict)); const mergeMap = RS.mergeMapWhen(EqStrict); return (stream) => pipe(use, RS.switchMapW(({ findRefs, deleteRefs }) => pipe(stream, RS.keyed(Eq), mergeMap((s) => pipe(s, RS.fromStream, RS.switchMapW(f), RS.onDispose(deleteRefs(s)), RS.useSomeWith(RS.fromEnv(findRefs(s)))))))); }; /** * @since 0.11.0 * @category Use */ export const useKVs = (f, Eq) => useKVStream(flow(f, KV.sample), Eq); //# sourceMappingURL=Use.js.map