Skip to content

Instantly share code, notes, and snippets.

@mattiamanzati
Created August 22, 2022 13:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattiamanzati/db03cd64f659dd73c052c1531833faa9 to your computer and use it in GitHub Desktop.
Save mattiamanzati/db03cd64f659dd73c052c1531833faa9 to your computer and use it in GitHub Desktop.
import * as T from "@effect/core/io/Effect"
import type * as EX from "@effect/core/io/Exit"
import * as FID from "@effect/core/io/FiberId"
import * as HUB from "@effect/core/io/Hub"
import type * as RU from "@effect/core/io/Runtime"
import * as S from "@effect/core/stream/Stream"
import * as C from "@effect-ts/core/Case"
import { pipe } from "@tsplus/stdlib/data/Function"
import * as O from "@tsplus/stdlib/data/Maybe"
import * as ENV from "@tsplus/stdlib/service/Env"
import { Tag } from "@tsplus/stdlib/service/Tag"
import * as React from "react"
const RuntimeContext = React.createContext<() => RU.Runtime<never>>(() => {
throw new Error("Missing top level runtime provider!")
})
export function RuntimeProvider(props: React.PropsWithChildren<{}>) {
const [runtime, setRuntime] = React.useState<O.Maybe<RU.Runtime<never>>>(O.none)
React.useEffect(() => {
const cancel = T.unsafeRunWith(
pipe(
T.runtime<never>(),
T.flatMap((value) => T.sync(() => setRuntime(O.some(value)))),
T.zipRight(T.never)
),
() => undefined
)
return () => cancel(FID.none)(() => undefined)
}, [])
const value = React.useCallback(() => O.toUndefined(runtime)!, [runtime])
if (O.isNone(runtime)) return null
return React.createElement(RuntimeContext.Provider, { value }, props.children)
}
export const RetryPolicyContext = React.createContext(
<R, E, A>(eff: T.Effect<R, E, A>): T.Effect<R, never, A> => T.orDie(eff)
)
export function useEffect<R, E, A>(eff: T.Effect<R, E, A>) {
const retryPolicy = React.useContext(RetryPolicyContext)
const runtime = React.useContext(RuntimeContext)
return React.useCallback(
(args: ENV.Env<R>, cb?: (ex: EX.Exit<E, A>) => void) => {
const cancel = runtime().unsafeRunWith(
pipe(retryPolicy(eff), T.provideEnvironment(args)),
cb ? cb : () => undefined
)
return () => cancel(FID.none)(() => undefined)
},
[eff, retryPolicy]
)
}
export function useEffectSingleton<R, E, A>(eff: T.Effect<R, E, A>) {
const [state, setState] = React.useState(0)
const _run = useEffect(eff)
const run = React.useCallback(
(args: ENV.Env<R>, cb?: (ex: EX.Exit<E, A>) => void) => {
setState((_) => _ + 1)
return _run(args, (exit) => {
if (cb) cb(exit)
setState((_) => _ - 1)
})
},
[_run]
)
return [run, state > 0] as const
}
export function useStreamLatestValue<A>(stream: S.Stream<never, never, A>): O.Maybe<A> {
const [state, setState] = React.useState<O.Maybe<A>>(O.none)
const eff = React.useMemo(
() =>
pipe(
stream,
S.changesWith((a, b) => a === b || JSON.stringify(a) === JSON.stringify(b)),
S.mapEffect((value) => T.succeedWith(() => setState(O.some(value)))),
S.runDrain
),
[stream, setState]
)
const runInterrupt = useEffect(eff)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-empty-function
React.useEffect(() => runInterrupt(ENV.Env.empty, () => {}), [eff, runInterrupt])
return state
}
export class RefetchAllEvent extends C.Tagged("RefetchAllEvent")<{}> {}
export type Event = RefetchAllEvent
export interface EventHub extends HUB.Hub<Event> {}
export const EventHub = Tag<EventHub>()
export const EventHubContext = React.createContext<EventHub>(null as any)
export function EventHubProvider(props: React.PropsWithChildren<{}>) {
const [hub, setHub] = React.useState<O.Maybe<EventHub>>(O.none)
const makeHub = useEffect(React.useMemo(() => HUB.unbounded<Event>(), []))
React.useEffect(
() =>
makeHub(ENV.Env.empty, (ex) =>
setHub((_) => (ex._tag === "Success" ? O.some(ex.value) : _))
),
[]
)
if (O.isNone(hub)) return null
return React.createElement(
EventHubContext.Provider,
{ value: hub.value },
props.children
)
}
export function invalidate<R, E, A>(
eff: T.Effect<R, E, A>
): T.Effect<R | EventHub, E, A> {
return pipe(
eff,
T.zipLeft(
T.serviceWithEffect(EventHub, (eventHub) =>
eventHub.publish(new RefetchAllEvent())
)
)
)
}
export function useQuery<R, E, A>(
fn: () => T.Effect<R, E, A>,
deps: any[]
): S.Stream<R, E, A> {
const eff = React.useMemo(fn, deps)
const eventHub = React.useContext(EventHubContext)
return React.useMemo(
() =>
pipe(
S.succeed(new RefetchAllEvent()),
S.concat(S.fromHub(eventHub, 1)),
S.flatMapParSwitch(1, () => S.fromEffect(eff))
),
[eff, eventHub]
)
}
export function useMutation<R, E, A>(
fn: () => T.Effect<R, E, A>,
deps: any[]
): T.Effect<R, E, A> {
return React.useMemo(fn, deps)
}
export function useMutations<NER extends Record<string, T.Effect<any, any, any>>>(
fn: () => NER,
deps: any[]
): NER {
return React.useMemo(() => {
return fn()
}, deps)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment