|
| 1 | +--- |
| 2 | +title: 'subscribe-ops' |
| 3 | +section: 'API' |
| 4 | +subSection: 'Advanced' |
| 5 | +description: 'Fine-grained mutation tracking with operational transformations (Ops)' |
| 6 | +--- |
| 7 | + |
| 8 | +# Subscribe Ops |
| 9 | + |
| 10 | +By default, Valtio's `subscribe` notify you that _something_ has changed in the state proxy. However, you can opt-in to receiving **Ops** (Operations), which are detailed descriptions of exactly what was modified. |
| 11 | + |
| 12 | +## What are Ops? |
| 13 | + |
| 14 | +Ops are granular mutation records. When a proxy is updated, Valtio can generate a description of the change as a tuple. |
| 15 | + |
| 16 | +### Op Types |
| 17 | + |
| 18 | +- **`set`**: `[op: 'set', path: Path, value: unknown, prevValue: unknown]` |
| 19 | + - Triggered when a property is assigned a new value. |
| 20 | +- **`delete`**: `[op: 'delete', path: Path, prevValue: unknown]` |
| 21 | + - Triggered when a property is deleted. |
| 22 | +- **`resolve`**: `[op: 'resolve', path: Path, value: unknown]` |
| 23 | + - Triggered when a promise in the state is fulfilled. |
| 24 | +- **`reject`**: `[op: 'reject', path: Path, error: unknown]` |
| 25 | + - Triggered when a promise in the state is rejected. |
| 26 | + |
| 27 | +The `Path` is an array of strings or symbols representing the nested location of the property (e.g., `['user', 'profile', 'name']`). |
| 28 | + |
| 29 | +## How to use Ops |
| 30 | + |
| 31 | +The "ops" feature is an **opt-in** feature because it introduces a small performance overhead for tracking and allocating these operation objects. |
| 32 | + |
| 33 | +### 1. Enabling Ops |
| 34 | + |
| 35 | +You must explicitly enable op-tracking using the unstable API: |
| 36 | + |
| 37 | +```javascript |
| 38 | +import { unstable_enableOp } from 'valtio' |
| 39 | + |
| 40 | +// Enable globally |
| 41 | +unstable_enableOp(true) |
| 42 | +``` |
| 43 | + |
| 44 | +### 2. Receiving Ops in `subscribe` |
| 45 | + |
| 46 | +Once enabled, the `subscribe` callback receives an array of these operations as its first argument. |
| 47 | + |
| 48 | +```javascript |
| 49 | +import { proxy, subscribe, unstable_enableOp } from 'valtio' |
| 50 | + |
| 51 | +unstable_enableOp(true) |
| 52 | + |
| 53 | +const state = proxy({ count: 0, text: 'hello' }) |
| 54 | + |
| 55 | +subscribe(state, (ops) => { |
| 56 | + ops.forEach((op) => { |
| 57 | + const [action, path, value, prevValue] = op |
| 58 | + console.log(`Action: ${action} at ${path.join('.')}`) |
| 59 | + console.log(`New value:`, value) |
| 60 | + console.log(`Previous value:`, prevValue) |
| 61 | + }) |
| 62 | +}) |
| 63 | + |
| 64 | +state.count++ |
| 65 | +// Output: |
| 66 | +// Action: set at count |
| 67 | +// New value: 1 |
| 68 | +// Previous value: 0 |
| 69 | +``` |
| 70 | + |
| 71 | +> **Note**: If `unstable_enableOp(true)` is not called, the `ops` argument will be an empty array or `undefined`. |
| 72 | +
|
| 73 | +## Use Cases |
| 74 | + |
| 75 | +While standard `subscribe` is sufficient for most React UI updates, Ops are useful for specific advanced scenarios: |
| 76 | + |
| 77 | +1. **Network Synchronization**: Instead of sending the entire state over the wire, you can send only the `ops` (patches). This significantly reduces bandwidth consumption in distributed applications. |
| 78 | +2. **Undo/Redo History**: Use the `prevValue` provided in `set` and `delete` ops to easily revert state changes. |
| 79 | +3. **Audit Logs & Debugging**: Track a sequence of user-driven mutations for analytics or time-travel debugging. |
| 80 | +4. **Devtools Integration**: Powering custom development tools that need to visualize state transitions. |
| 81 | + |
| 82 | +## Performance Considerations |
| 83 | + |
| 84 | +Enabling Ops has a small overhead cost. For every mutation, Valtio must: |
| 85 | + |
| 86 | +- Detect the change type. |
| 87 | +- Construct the `Path` array. |
| 88 | +- Allocate the `Op` tuple. |
| 89 | + |
| 90 | +In high-frequency update scenarios (e.g., animations, canvas interactions moving hundreds of objects per frame), this can lead to: |
| 91 | + |
| 92 | +- Increased garbage collection (GC) pressure due to object allocations. |
| 93 | +- A measurable drop in frame rates (FPS). |
| 94 | + |
| 95 | +**Recommendation**: Only enable `unstable_enableOp` if your application actually consumes the granular `ops` data. |
0 commit comments