UNPKG

seitu

Version:
168 lines (116 loc) • 3.96 kB
# React (seitu/react) ## useSubscription Core React hook from `seitu/react`. Built on `useSyncExternalStore` for concurrent-safe reads. Works with any `Subscribable<T> & Readable<T>`. ### Basic usage (module-level instance) ```tsx 'use client' import { useSubscription } from 'seitu/react' import { createStore } from 'seitu' const count = createStore(0) function Counter() { const value = useSubscription(count) return <button onClick={() => count.set(v => v + 1)}>{value}</button> } ``` ### Factory function (inline creation) ```tsx 'use client' import { useSubscription } from 'seitu/react' import { createScrollState } from 'seitu/web' import { useState } from 'react' function ScrollTracker() { // Callback ref + deps: subscription is rebuilt on mount/unmount/remount. const [el, setEl] = useState<HTMLDivElement | null>(null) const state = useSubscription( () => createScrollState({ element: el, direction: 'vertical' }), { deps: [el] } ) return <div ref={setEl}>{state.top.reached ? 'at top' : 'scrolled'}</div> } ``` ### With selector (granular re-renders) ```tsx const count = useSubscription(storage, { selector: v => v.count }) ``` ### With deps (re-create on prop change) ```tsx function UserStorage({ userId }: { userId: string }) { const data = useSubscription( () => createWebStorageValue({ type: 'localStorage', key: `user:${userId}`, schema, defaultValue }), { deps: [userId] }, ) } ``` ### Options | Option | Type | Description | |--------|------|-------------| | `selector?` | `(value) => R` | Derive subset; re-render only when it changes | | `deps?` | `DependencyList` | Re-create factory subscription when deps change | | `isEqual?` | `(prev, next) => boolean` | Custom equality (default: `deepEqual`) | ## Subscription (render-prop component) Declarative render-prop component from `seitu/react`. Isolates re-renders to the children function. ```tsx import { Subscription } from 'seitu/react' <Subscription value={storage}> {(value) => <div>{value.count}</div>} </Subscription> <Subscription value={storage} selector={v => v.count}> {(count) => <div>{count}</div>} </Subscription> ``` ### Props | Prop | Type | Description | |------|------|-------------| | `value` | `Subscribable & Readable` | The reactive source | | `selector?` | `(value) => R` | Optional selector for granular updates | | `children` | `(value) => ReactNode` | Render function | ## Common Mistakes ### [CRITICAL] Missing use client directive Wrong: ```ts export default function Page() { const v = useSubscription(store) } ``` Correct: ```ts 'use client' export default function Page() { const v = useSubscription(store) } ``` Hook (and the `Subscription` component, which uses it internally) uses client-only APIs; fails in RSC server components. ### [CRITICAL] Factory without deps when props change Wrong: ```ts useSubscription(() => createWebStorageValue({ key: userId, ... })) ``` Correct: ```ts useSubscription(() => createWebStorageValue({ key: userId, ... }), { deps: [userId] }) ``` Factory runs once unless deps array changes. Same applies to a `Subscription` inline source — key it or pass deps. ### [HIGH] Using useEffect to subscribe manually Wrong: ```ts useEffect(() => store.subscribe(setV), []) ``` Correct: ```ts const v = useSubscription(store) ``` useSubscription handles subscribe/unsubscribe and concurrent safety. ### [LOW] Preferring component over hook without reason Wrong: ```ts <Subscription source={s}>{v => <Child value={v} />}</Subscription> ``` Correct: ```ts const v = useSubscription(s); return <Child value={v} /> ``` useSubscription is simpler for most cases; Subscription is for render-prop composition (isolating re-renders to a subtree). ## See also - [`create-scroll-state`](create-scroll-state.md) — Scroll tracking requires factory + ref pattern in React. ## Source `src/react/hooks/index.ts`, `src/react/components/index.tsx`