@@ -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<A>` 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>()("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 <p>Doubled: {doubled}</p>
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
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<A>` is a `Subscribable<A>` 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>()("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 (
|
||||||
|
<input
|
||||||
|
value={name}
|
||||||
|
onChange={(event) => 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).
|
||||||
@@ -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...)
|
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||||
|
|
||||||
const sidebars: SidebarsConfig = {
|
const sidebars: SidebarsConfig = {
|
||||||
docsSidebar: ["getting-started"],
|
docsSidebar: ["getting-started", "state-management"],
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sidebars
|
export default sidebars
|
||||||
|
|||||||
Reference in New Issue
Block a user