From 9853cf8d43811787f3f6763d44285a776f1bba88 Mon Sep 17 00:00:00 2001 From: enyelsequeira Date: Mon, 26 May 2025 17:41:58 +0100 Subject: [PATCH] chore: adding more in depth docs for main features --- docs/config.json | 20 ++++ docs/core-derived.md | 123 +++++++++++++++++++++++ docs/core-effect.md | 41 ++++++++ docs/core-store.md | 226 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 410 insertions(+) create mode 100644 docs/core-derived.md create mode 100644 docs/core-effect.md create mode 100644 docs/core-store.md diff --git a/docs/config.json b/docs/config.json index f7f620e8..cc7251ae 100644 --- a/docs/config.json +++ b/docs/config.json @@ -21,6 +21,7 @@ "label": "Quick Start", "to": "quick-start" } + ], "frameworks": [ { @@ -70,6 +71,25 @@ } ] }, + { + "label": "Guides & Concepts", + "children": [ + { + "label": "Store", + "to": "core-store" + }, + { + "label": "Derived", + "to": "core-derived" + }, + { + "label": "Effect", + "to": "core-effect" + } + ] + + }, + { "label": "API Reference", "children": [ diff --git a/docs/core-derived.md b/docs/core-derived.md new file mode 100644 index 00000000..184ae8cf --- /dev/null +++ b/docs/core-derived.md @@ -0,0 +1,123 @@ +--- +title: Core Derived +id: core-derived +--- + +# Derived Documentation + + +The `Derived` class creates computed values that automatically update when their dependencies change. This solves the diamond dependency problem through intelligent batching and dependency tracking. + +```typescript +import { Derived } from '@tanstack/store' + +const doubled = new Derived({ + deps: [counterStore], + fn: () => counterStore * 2 +}) +``` + +**Key Features:** +- **Automatic Updates**: Recalculates when dependencies change +- **Diamond Dependency Resolution**: Prevents duplicate calculations in complex dependency graphs +- **Lazy Evaluation**: Only computes when subscribed to or explicitly mounted +- **Lazy Evaluation**:Access to both current and previous values of dependencies and the derived value itself + + + + +### Derived Options + +#### `DerivedOptions` +Configuration object for creating derived values. + +```typescript +interface DerivedOptions { + deps: TArr // Array of Store or Derived dependencies + fn: (props: DerivedFnProps) => TState // Computation function + onSubscribe?: (listener: Listener, derived: Derived) => () => void + onUpdate?: () => void // Called after each recomputation +} +``` + +#### `DerivedFnProps` +Props passed to the derived computation function. + +```typescript +interface DerivedFnProps { + prevVal: unknown | undefined // Previous derived value (undefined on first run) + prevDepVals: TUnwrappedArr | undefined // Previous dependency values + currDepVals: TUnwrappedArr // Current dependency values +} +``` + +Example with previous values: + +```typescript +const changeTracker = new Derived({ + deps: [counterStore], + fn: ({ currDepVals, prevDepVals, prevVal }) => { + const currentCounter = currDepVals[0] + const prevCounter = prevDepVals?.[0] + + + return { + current: currentCounter, + previous: prevCounter, + hasChanged: prevCounter !== currentCounter, + previousResult: prevVal + } + } +}) +``` + +### Derived Methods + +#### `mount()` +Registers the derived value in the dependency graph and returns cleanup function. + +```typescript +derived.mount() +``` + +#### `recompute()` +Manually triggers recomputation of the derived value. + +```typescript +derived.recompute() +``` + +#### `subscribe(listener)` +Subscribes to derived value changes. + +```typescript +const unsubscribe = derived.subscribe(({ prevVal, currentVal }) => { + // Handle derived value change +}) +``` + +#### `checkIfRecalculationNeededDeeply()` +Recursively checks all derived dependencies and recomputes if any dependency values have changed.. + +```typescript +derived.checkIfRecalculationNeededDeeply() +``` + +#### `registerOnGraph(deps?) +Registers this derived value in the dependency graph with specified dependencies (defaults to this.options.deps). + + +```typescript +derived.registerOnGraph() +// or with custom deps +derived.registerOnGraph([customStore]) +``` + + +#### `unregisterFromGraph(deps?)` +Removes this derived value from the dependency graph for specified dependencies. + +```typescript +derived.unregisterFromGraph() + +``` diff --git a/docs/core-effect.md b/docs/core-effect.md new file mode 100644 index 00000000..7470c26c --- /dev/null +++ b/docs/core-effect.md @@ -0,0 +1,41 @@ +--- +title: Core Effect +id: core-effect +--- + +# Effect Documentation + +The `Effect` class runs side effects when dependencies change, similar to `useEffect` in React. + +```typescript +import { Effect } from '@tanstack/store' + +const logEffect = new Effect({ + deps: [counterStore], + fn: () => console.log('Counter changed:', counterStore.state), + eager: true // Run immediately on creation +}) +``` + +### Effect Options + +#### `EffectOptions` +Configuration for creating effects. + +```typescript +interface EffectOptions { + deps: ReadonlyArray | Store> // Dependencies to watch + fn: () => void // Effect function to run + eager?: boolean // Whether to run immediately (default: false) +} +``` + +### Effect Methods + +#### `mount()` +Activates the effect + +```typescript +effect.mount() + +``` diff --git a/docs/core-store.md b/docs/core-store.md new file mode 100644 index 00000000..b26e152e --- /dev/null +++ b/docs/core-store.md @@ -0,0 +1,226 @@ +--- +title: Core Store +id: core-store +--- + +# TanStack Store Documentation + +A reactive state management library with derived values, effects, and batched updates. + +## Core Concepts + +### Store + +The `Store` class is the fundamental building block for managing state. It provides reactive state management with subscription capabilities and integrates with the batching system for optimal performance + +```typescript +import { Store } from '@tanstack/store' + +// Create a store with initial state +const counterStore = new Store(0) + +counterStore.setState(prev => prev + 1) +``` +**Key Features:** +- **Reactive State Management**: Automatically notifies listeners when state changes +- **Custom Update Functions**: Support for custom state update logic +- **Lifecycle Hooks**: Callbacks for subscription and update events +- **Previous State Tracking**:Access to both current and previous state values + +## Store Options + +### `StoreOptions` +Configuration object for customizing store behavior. + +```typescript +interface StoreOptions { + updateFn?: (previous: TState) => (updater: TUpdater) => TState // Custom update function + onSubscribe?: (listener: Listener, store: Store) => () => void // Called when listener subscribes + onUpdate?: () => void // Called after state updates +} +``` + +**Example with options:** +```typescript +const customStore = new Store(0, { + updateFn: (prev) => (updater) => { + // Custom update logic - e.g., validation, transformation + const newValue = updater(prev) + return Math.max(0, newValue) // Ensure value is never negative + }, + onSubscribe: (listener, store) => { + console.log('New subscriber added') + return () => console.log('Subscriber removed') + }, + onUpdate: () => { + console.log('Store updated:', customStore.state) + } +}) +``` + + + +## Core Types + +### `AnyUpdater` +```typescript +type AnyUpdater = (prev: any) => any +``` +Function type for state updates that receive the previous value and return the new value. + +### `Listener` +```typescript +type Listener = (value: ListenerValue) => void + +interface ListenerValue { + prevVal: T + currentVal: T +} +``` +Function type for subscribing to state changes. Receives both previous and current values. + +## Store Methods + +### `setState(updater)` +Updates the store's state, triggers the update lifecycle, and flushes changes through the batching system. + +```typescript +// Function updater (recommended) +store.setState(prev => prev + 1) + +``` + +**Behavior:** +- Updates `prevState` to the current `state` +- Computes new state using the updater function or custom `updateFn` +- Calls `onUpdate` callback if provided +- Triggers the flush mechanism to update derived values and notify listeners + +### `subscribe(listener)` +Subscribes to state changes and returns an unsubscribe function. + +```typescript +store.subscribe(({ prevVal, currentVal }) => { + console.log(`Changed from ${prevVal} to ${currentVal}`) +}) + +``` + +**Behavior:** +- Adds listener to the store's listener set +- Calls `onSubscribe` callback if provided + +## Batching Integration + +The Store class works seamlessly with the `batch` function to optimize performance in complex update scenarios. + +### `batch(fn)` +Groups multiple state updates to prevent intermediate calculations and cascade updates. + +```typescript +import { batch } from '@tanstack/store' + +const store1 = new Store(0) +const store2 = new Store(10) + +// Without batching - triggers updates twice +store1.setState(prev => prev + 1) // Derived values recalculate +store2.setState(prev => prev * 2) // Derived values recalculate again + +// With batching - triggers updates only once at the end +batch(() => { + store1.setState(prev => prev + 1) + store2.setState(prev => prev * 2) + // All derived values recalculate only once after both updates +}) +``` + +**How batching works with stores:** +1. During batch, `setState` calls are queued instead of immediately flushed +2. Store listeners are notified with the initial pre-batch values as `prevVal` +3. Derived values are recalculated only once after all batched updates complete +4. This prevents the diamond dependency problem and improves performance + +## Usage Examples + +### Basic Counter +```typescript +const counterStore = new Store(0) + +// Subscribe to changes +const unsubscribe = counterStore.subscribe(({ prevVal, currentVal }) => { + console.log(`Counter: ${prevVal} → ${currentVal}`) +}) + +// Update the counter +counterStore.setState(prev => prev + 1) // Logs: "Counter: 0 → 1" +counterStore.setState(prev => prev + 5) // Logs: "Counter: 1 → 6" +``` + +### Store with Validation +```typescript +const validatedStore = new Store('', { + updateFn: (prev) => (updater) => { + const newValue = updater(prev) + // Only allow non-empty strings + return newValue.trim() || prev + }, + onUpdate: () => { + if (validatedStore.state.length > 0) { + console.log('Valid input:', validatedStore.state) + } + } +}) + +validatedStore.setState(() => ' ') // Rejected, stays empty +validatedStore.setState(() => 'Hello') // Accepted: "Hello" +``` + +### Complex State Updates with Batching +```typescript +const userStore = new Store({ name: '', age: 0 }) +const settingsStore = new Store({ theme: 'light', notifications: true }) + +// Update multiple stores efficiently +batch(() => { + userStore.setState(prev => ({ ...prev, name: 'John', age: 25 })) + settingsStore.setState(prev => ({ ...prev, theme: 'dark' })) + // Any derived values depending on both stores update only once +}) +``` + +## Best Practices + +### 1. Use Function Updaters +```typescript +// ✅ Good: Safe and predictable +store.setState(prev => prev + 1) + +// ❌ Avoid: Direct mutations +store.state++ // This won't trigger updates +``` + +### 2. Batch Related Updates +```typescript +// ✅ Good: Single update cycle +batch(() => { + store1.setState(prev => prev + 1) + store2.setState(prev => prev * 2) +}) + +// ❌ Less efficient: Multiple update cycles +store1.setState(prev => prev + 1) +store2.setState(prev => prev * 2) +``` + + +### 3. Use onUpdate for Side Effects +```typescript +// ✅ Good: Centralized side effects +const store = new Store(data, { + onUpdate: () => { + // Save to localStorage, sync with server, etc. + localStorage.setItem('data', JSON.stringify(store.state)) + } +}) +```