Skip to content

Commit 13f4cc3

Browse files
committed
Add two more documentation bits based on Berni
1 parent e828e37 commit 13f4cc3

File tree

2 files changed

+95
-7
lines changed

2 files changed

+95
-7
lines changed

docs/common/PATTERNS.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,48 @@ const createChat = handler<unknown, { chatsList: Cell<Entry[]> }>(
789789

790790
**Reference:** See `packages/patterns/chatbot-list-view.tsx` for the canonical implementation, specifically the `storeCharm` lift and `createChatRecipe` handler. Full details in `HANDLERS.md` under "Storing Pattern Instances in Cell Arrays".
791791

792+
#### 6. Not Including All Cells in Derive Dependencies
793+
794+
```typescript
795+
// ❌ WRONG - items is used in callback but not in dependencies
796+
{derive(itemCount, (count) =>
797+
count === 0 ? (
798+
<div>No items yet</div>
799+
) : (
800+
<div>
801+
{items.map((item) => <div>{item.title}</div>)}
802+
</div>
803+
)
804+
)}
805+
// Error: Shadow ref alias with parent cell not found in current frame
806+
807+
// ✅ CORRECT - Include all cells referenced in the callback
808+
{derive([itemCount, items] as const, ([count, itemsList]: [number, Item[]]) =>
809+
count === 0 ? (
810+
<div>No items yet</div>
811+
) : (
812+
<div>
813+
{itemsList.map((item) => <div>{item.title}</div>)}
814+
</div>
815+
)
816+
)}
817+
818+
// ✅ ALTERNATIVE - Use direct ternary if you don't need derive's reactivity
819+
{itemCount === 0 ? (
820+
<div>No items yet</div>
821+
) : (
822+
<div>
823+
{items.map((item) => <div>{item.title}</div>)}
824+
</div>
825+
)}
826+
```
827+
828+
**Why this is a pitfall:** When a derive callback closes over cells (references them from the outer scope), those cells must be included in the dependency array. Otherwise, you'll get cryptic "Shadow ref" errors.
829+
830+
**Rule:** If your derive callback uses any cells, include them all in the dependency array: `derive([cell1, cell2, ...], ([val1, val2, ...]) => ...)`.
831+
832+
**Note:** Values inside derive callbacks are read-only. If you need bidirectional binding (like `$checked`), use cells directly outside the derive instead.
833+
792834
## Testing Patterns and Development Workflow
793835

794836
### Quick Development Workflow

docs/common/RECIPES.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,11 @@ logic.
426426
});
427427
```
428428

429-
7. **Type Array Map Parameters as OpaqueRef**: When mapping over cell arrays with bidirectional binding, you **must** add the `OpaqueRef<T>` type annotation to make it type-check correctly:
429+
7. **Type Array Map Parameters When Needed**: When mapping over cell arrays, TypeScript usually infers types correctly, but you may need to add type annotations in some cases:
430430

431431
```typescript
432-
// ✅ CORRECT - Type as OpaqueRef for bidirectional binding
433-
{items.map((item: OpaqueRef<ShoppingItem>) => (
432+
// ✅ USUALLY WORKS - Type inference handles most cases
433+
{items.map((item) => (
434434
<div>
435435
<ct-checkbox $checked={item.done}>
436436
<span>{item.title}</span>
@@ -439,13 +439,22 @@ logic.
439439
</div>
440440
))}
441441
442-
// ❌ INCORRECT - Missing type leads to type errors with $-props
443-
{items.map((item) => (
444-
<ct-checkbox $checked={item.done} /> // Type error!
442+
// ✅ ADD TYPE IF NEEDED - For complex scenarios or type errors
443+
{items.map((item: OpaqueRef<ShoppingItem>) => (
444+
<ct-checkbox $checked={item.done}>
445+
<span>{item.title}</span>
446+
</ct-checkbox>
445447
))}
446448
```
447449

448-
**Why is this needed?** When you use `.map()` on a Cell array, TypeScript cannot always infer the correct type for bidirectional binding properties. The `OpaqueRef<T>` annotation tells TypeScript that each item is a cell-like reference that supports property access and bidirectional binding.
450+
**When to add types:**
451+
- If you see TypeScript errors with bidirectional binding (`$-props`)
452+
- When working with complex nested structures
453+
- When TypeScript cannot infer the correct type
454+
455+
**When to skip types:**
456+
- Most simple cases work fine without explicit types
457+
- The framework usually handles type inference correctly
449458

450459
8. **Understand When Conditionals Work in JSX**: Ternary operators work fine in JSX **attributes**, but you need `ifElse()` for conditional **rendering** and **data transformations**:
451460

@@ -529,6 +538,43 @@ logic.
529538

530539
**When to use derive:** When you're computing a single value from specific cells.
531540

541+
**⚠️ Include All Closed-Over Cells in Derive Dependencies**: When your derive callback references cells, you must include them in the dependency array:
542+
543+
```typescript
544+
// ❌ INCORRECT - items is closed over but not in dependencies
545+
{derive(itemCount, (count) =>
546+
count === 0 ? (
547+
<div>No items yet</div>
548+
) : (
549+
<div>
550+
{items.map((item) => <div>{item.title}</div>)}
551+
</div>
552+
)
553+
)}
554+
555+
// ✅ CORRECT - Include all cells used in the callback
556+
{derive([itemCount, items] as const, ([count, itemsList]: [number, Item[]]) =>
557+
count === 0 ? (
558+
<div>No items yet</div>
559+
) : (
560+
<div>
561+
{itemsList.map((item) => <div>{item.title}</div>)}
562+
</div>
563+
)
564+
)}
565+
566+
// Alternative: Use ternary directly if you don't need reactivity
567+
{itemCount === 0 ? (
568+
<div>No items yet</div>
569+
) : (
570+
<div>
571+
{items.map((item) => <div>{item.title}</div>)}
572+
</div>
573+
)}
574+
```
575+
576+
**Note**: Items inside derive callbacks are read-only. For bidirectional binding (like `$checked`), use the cells directly outside the derive, or structure your code to avoid needing mutable access inside the callback.
577+
532578
10. **Access Properties Directly on Derived Objects**: You can access properties on derived objects without additional helpers:
533579

534580
```typescript

0 commit comments

Comments
 (0)