UNPKG

seitu

Version:
122 lines (121 loc) • 3.34 kB
import { deepEqual } from "fast-equals"; import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; //#region src/solid/hooks/index.ts /** * Subscribe to a reactive value. Pass a handle or a getter (reactive: * re-subscribes when it reads a signal). Returns an `Accessor<R>` (`value()`). * * @example Inline subscription * ```tsx * import { createWebStorageValue } from 'seitu/web' * import { useSubscription } from 'seitu/solid' * import * as z from 'zod' * * function Counter() { * const value = useSubscription(() => createWebStorageValue({ * type: 'sessionStorage', * key: 'test', * defaultValue: 0, * schema: z.number(), * })) * * return <div>{value()}</div> * } * ``` * * @example Instance outside of component * ```tsx * import { createWebStorage } from 'seitu/web' * import { useSubscription } from 'seitu/solid' * import * as z from 'zod' * * const sessionStorage = createWebStorage({ * type: 'sessionStorage', * schemas: { count: z.number(), name: z.string() }, * defaultValues: { count: 0, name: '' }, * }) * * function Counter() { * const value = useSubscription(sessionStorage) * return <div>{value().count}</div> * } * ``` * * @example Subscription with selector * ```tsx * import { createWebStorage } from 'seitu/web' * import { useSubscription } from 'seitu/solid' * import * as z from 'zod' * * const sessionStorage = createWebStorage({ * type: 'sessionStorage', * schemas: { * count: z.number(), * name: z.string(), * }, * defaultValues: { count: 0, name: '' }, * }) * * function Counter() { * // Accessor updates only when count changes * const count = useSubscription(sessionStorage, { selector: value => value.count }) * * return <div>{count()}</div> * } * ``` * * @example Reactive source (re-subscribes when a signal changes) * ```tsx * import { createSignal } from 'solid-js' * import { createWebStorageValue } from 'seitu/web' * import { useSubscription } from 'seitu/solid' * import * as z from 'zod' * * function User() { * const [userId, setUserId] = createSignal('user-1') * const data = useSubscription(() => createWebStorageValue({ * type: 'localStorage', * key: `user:${userId()}`, * schema: z.object({ name: z.string() }), * defaultValue: { name: '' }, * })) * * return <div>{data().name}</div> * } * ``` * * @example Ref example * ```tsx * import { createScrollState } from 'seitu/web' * import { useSubscription } from 'seitu/solid' * * function Page() { * let ref: HTMLDivElement | undefined * const state = useSubscription(() => createScrollState({ element: () => ref, direction: 'vertical' })) * * return ( * <div ref={ref}> * {String(state().top.reached)} * </div> * ) * } * ``` */ function useSubscription(source, options) { const { selector, isEqual = deepEqual } = options ?? {}; const getSource = typeof source === "function" ? source : () => source; function getSnapshot(sub) { return selector ? selector(sub.get()) : sub.get(); } const sub = createMemo(getSource); const [state, setState] = createSignal(getSnapshot(sub()), { equals: isEqual }); createEffect(() => { const current = sub(); const update = () => setState(() => getSnapshot(current)); update(); onCleanup(current.subscribe(update)); }); return state; } //#endregion export { useSubscription };