UNPKG

effectts-react

Version:
323 lines (238 loc) 6.55 kB
# effectts-react [![CI](https://github.com/k70suK3-k06a7ash1/effectts-react/actions/workflows/ci.yml/badge.svg)](https://github.com/k70suK3-k06a7ash1/effectts-react/actions/workflows/ci.yml) React hooks for Effect-TS ## Installation ```bash npm install effectts-react # or yarn add effectts-react # or pnpm add effectts-react ``` ## Requirements - React 18+ - Effect-TS 3+ ## Usage ### useEffectQuery Run an Effect and get its result in your React component: ```typescript import { useEffectQuery } from 'effectts-react'; import * as Effect from 'effect/Effect'; function MyComponent() { const { data, error, loading } = useEffectQuery( Effect.succeed('Hello, Effect!'), [] ); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return <div>{data}</div>; } ``` ### useRuntime Create a runtime for running Effects: ```typescript import { useRuntime } from 'effectts-react'; import * as Effect from 'effect/Effect'; function MyComponent() { const runtime = useRuntime(); const handleClick = () => { const effect = Effect.sync(() => console.log('Clicked!')); Effect.runPromise(effect); }; return <button onClick={handleClick}>Click me</button>; } ``` ### usePoll Run an Effect repeatedly at a specified interval: ```typescript import { usePoll } from 'effectts-react'; import * as Effect from 'effect/Effect'; function MyComponent() { const { data, error, loading } = usePoll( Effect.sync(() => new Date().toISOString()), 1000, // Run every 1 second [] ); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return <div>Current time: {data}</div>; } ``` ### useEffectRef Manage mutable state with Effect Ref for safe concurrent access: ```typescript import { useEffectRef } from 'effectts-react'; function Counter() { const { value, loading, set, update } = useEffectRef(0); if (loading) return <div>Loading...</div>; return ( <div> <p>Count: {value}</p> <button onClick={() => update(n => n + 1)}>Increment</button> <button onClick={() => update(n => n - 1)}>Decrement</button> <button onClick={() => set(0)}>Reset</button> </div> ); } ``` ### useSynchronizedRef Perform atomic, effectful state updates with SynchronizedRef: ```typescript import { useSynchronizedRef } from 'effectts-react'; import * as Effect from 'effect/Effect'; function UserList() { const { value, loading, updateEffect } = useSynchronizedRef<string[]>([]); const fetchAndAddUser = async () => { await updateEffect(users => Effect.gen(function* () { // Simulate fetching user data const response = yield* Effect.promise(() => fetch('/api/user').then(r => r.json()) ); return [...users, response.name]; }) ); }; if (loading) return <div>Loading...</div>; return ( <div> <button onClick={fetchAndAddUser}>Add User</button> <ul> {value?.map((user, i) => <li key={i}>{user}</li>)} </ul> </div> ); } ``` ### useSubscriptionRef Reactive state management with automatic change notifications: ```typescript import { useSubscriptionRef } from 'effectts-react'; function ReactiveCounter() { const { value, loading, update } = useSubscriptionRef(0); // Value automatically updates when the ref changes if (loading) return <div>Loading...</div>; return ( <div> <p>Count: {value}</p> <button onClick={() => update(n => n + 1)}>Increment</button> </div> ); } ``` ## API ### `useEffectQuery<A, E>(effect: Effect.Effect<A, E>, deps?: DependencyList)` Runs an Effect and returns its result. **Parameters:** - `effect`: The Effect to run - `deps`: Dependency array (like React's useEffect) **Returns:** ```typescript { data: A | null; error: E | null; loading: boolean; } ``` ### `useRuntime<R>(context?: Context.Context<R>)` Creates a runtime for running Effects. **Parameters:** - `context`: Optional context to provide to the runtime **Returns:** Runtime instance ### `usePoll<A, E>(effect: Effect.Effect<A, E>, intervalMs: number, deps?: DependencyList)` Runs an Effect repeatedly at a specified interval. **Parameters:** - `effect`: The Effect to run - `intervalMs`: Interval in milliseconds - `deps`: Dependency array **Returns:** ```typescript { data: A | null; error: E | null; loading: boolean; } ``` ### `useEffectRef<A>(initialValue: A)` Creates a mutable reference with Effect Ref for safe concurrent state management. **Parameters:** - `initialValue`: The initial value for the Ref **Returns:** ```typescript { value: A | null; loading: boolean; get: () => Promise<A>; set: (value: A) => Promise<void>; update: (f: (a: A) => A) => Promise<void>; modify: <B>(f: (a: A) => readonly [B, A]) => Promise<B>; } ``` ### `useSynchronizedRef<A>(initialValue: A)` Creates a SynchronizedRef for atomic, effectful state updates. **Parameters:** - `initialValue`: The initial value for the SynchronizedRef **Returns:** ```typescript { value: A | null; loading: boolean; get: () => Promise<A>; set: (value: A) => Promise<void>; update: (f: (a: A) => A) => Promise<void>; updateEffect: <R, E>(f: (a: A) => Effect.Effect<A, E, R>) => Promise<void>; modify: <B>(f: (a: A) => readonly [B, A]) => Promise<B>; } ``` ### `useSubscriptionRef<A>(initialValue: A)` Creates a SubscriptionRef with automatic change notifications via reactive streams. **Parameters:** - `initialValue`: The initial value for the SubscriptionRef **Returns:** ```typescript { value: A | null; loading: boolean; get: () => Promise<A>; set: (value: A) => Promise<void>; update: (f: (a: A) => A) => Promise<void>; updateEffect: <R, E>(f: (a: A) => Effect.Effect<A, E, R>) => Promise<void>; modify: <B>(f: (a: A) => readonly [B, A]) => Promise<B>; } ``` ## Development ### Running Tests ```bash npm test # or make test ``` ### Type Checking ```bash npm run typecheck # or make typecheck ``` ### Building ```bash npm run build # or make build ``` ### Publishing This project uses an Effect-TS pipeline for automated publishing. See [PUBLISHING.md](./PUBLISHING.md) for details. Quick publish commands: ```bash # Publish a patch version (0.1.0 → 0.1.1) make publish-patch # Publish a minor version (0.1.0 → 0.2.0) make publish-minor # Publish a major version (0.1.0 → 1.0.0) make publish-major ``` Test with dry-run mode: ```bash npx tsx scripts/publish.ts patch --dry-run ``` ## License MIT