diff --git a/packages/docs/docs/state-management.md b/packages/docs/docs/state-management.md new file mode 100644 index 0000000..cd62f75 --- /dev/null +++ b/packages/docs/docs/state-management.md @@ -0,0 +1,114 @@ +--- +sidebar_position: 2 +title: State Management +--- + +# State Management + +`effect-fc` works well with state that lives in Effect services, layers, and +scopes. The usual pattern is: + +1. Create state with Effect primitives such as `SubscriptionRef`. +2. Expose read-only reactive state as a `Subscribable`. +3. Expose read/write reactive state as a `Lens`. +4. Bind values into components with `Subscribable.useAll`. + +`effect-fc` re-exports the `Lens` and `Subscribable` modules from +[`effect-lens`](https://github.com/Thiladev/effect-lens) for convenience. The +core data model and transformation APIs belong to `effect-lens`, so check the +`effect-lens` documentation for the full Lens/Subscribable API. + +## Subscribables + +A `Subscribable` is reactive state with a current value and a stream of +changes. Use `Subscribable.useAll` whenever a component needs to bind +subscribable values into render output. + +`Lens` is a `Subscribable`, so this is also the default way to read Lens values +from a component. + +```tsx +import { Effect, SubscriptionRef } from "effect" +import { Component, Lens, Subscribable } from "effect-fc" + +class CounterState extends Effect.Service()("CounterState", { + effect: Effect.gen(function* () { + const count = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(0)) + const doubled = Subscribable.map(count, (n) => n * 2) + + return { count, doubled } as const + }), +}) {} + +const CounterReadOnlyView = Component.make("CounterReadOnly")( + function* () { + const state = yield* CounterState + const [doubled] = yield* Subscribable.useAll([state.doubled]) + + return

Doubled: {doubled}

+ }, +) +``` + +Use `Subscribable.useAll` instead of subscribing manually. It reads the current +values during render and uses scoped subscriptions to update React state when +changes arrive. + +## Lenses + +A `Lens
` is a `Subscribable` that can also be written to. If a component +only needs to display the value, keep using `Subscribable.useAll`. + +Use `Lens.useState` when React needs a read/write tuple, especially for inputs +that require synchronous updates such as controlled text inputs. + +```tsx +import { Effect, SubscriptionRef } from "effect" +import { Component, Lens } from "effect-fc" + +class FormState extends Effect.Service()("FormState", { + effect: Effect.gen(function* () { + const name = Lens.fromSubscriptionRef(yield* SubscriptionRef.make("")) + + return { name } as const + }), +}) {} + +const NameInputView = Component.make("NameInput")(function* () { + const state = yield* FormState + const [name, setName] = yield* Lens.useState(state.name) + + return ( + setName(event.currentTarget.value)} + /> + ) +}) +``` + +`Lens.useState` returns the current value and a React-compatible setter. Calling +the setter writes through the Lens, so every other component subscribed to the +same Lens sees the update. + +## Choosing One + +Use `Subscribable.useAll` when render needs to read values: + +```tsx +const [count, doubled] = yield* Subscribable.useAll([ + state.count, + state.doubled, +]) +``` + +Use `Lens.useState` when JSX needs both the current value and a synchronous +setter: + +```tsx +const [name, setName] = yield* Lens.useState(state.name) +``` + +For focusing into nested state, deriving lenses, custom write behavior, and the +complete API, refer to the +[`effect-lens` documentation](https://github.com/Thiladev/effect-lens). diff --git a/packages/docs/sidebars.ts b/packages/docs/sidebars.ts index 7efd336..e774757 100644 --- a/packages/docs/sidebars.ts +++ b/packages/docs/sidebars.ts @@ -3,7 +3,7 @@ import type { SidebarsConfig } from "@docusaurus/plugin-content-docs" // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const sidebars: SidebarsConfig = { - docsSidebar: ["getting-started"], + docsSidebar: ["getting-started", "state-management"], } export default sidebars