docs(record): add bilingual usage guides
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Nested contexts and stable identity
|
||||
description: Compose local record contexts and select stable React keys for record lists.
|
||||
order: 20
|
||||
toc:
|
||||
- id: nested-records
|
||||
title: Nested records
|
||||
- id: optional-provider
|
||||
title: Optional provider
|
||||
- id: stable-keys
|
||||
title: Stable identity
|
||||
---
|
||||
|
||||
## Nested records {#nested-records}
|
||||
|
||||
Providers can be nested. Fields inside the inner provider read the local record, and the outer record becomes active again outside that boundary.
|
||||
|
||||
```tsx
|
||||
<RecordProvider value={order}>
|
||||
<OrderNumber />
|
||||
|
||||
<RecordProvider value={order.customer}>
|
||||
<CustomerName />
|
||||
</RecordProvider>
|
||||
</RecordProvider>
|
||||
```
|
||||
|
||||
Do not use record context as a mutable store. Pass a new record reference when new data arrives, and keep editable values in a form or another state layer.
|
||||
|
||||
## Optional provider {#optional-provider}
|
||||
|
||||
`OptionalRecordProvider` creates a new boundary only when `value` is neither `undefined` nor `null`. A nullish value preserves the nearest parent record, while `0`, `false`, and an empty string still create a new boundary. This is useful for components that accept a prop but can also compose into an existing record view.
|
||||
|
||||
```tsx
|
||||
function ProductCard({ product }: { product?: Product }) {
|
||||
return (
|
||||
<OptionalRecordProvider value={product}>
|
||||
<ProductTitle />
|
||||
<ProductPrice />
|
||||
</OptionalRecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Stable identity {#stable-keys}
|
||||
|
||||
`RecordsIterator` uses string, number, and bigint records directly as keys. For object records it reads `id` by default. Other values and objects without an `id` can provide a `recordKey` callback; objects can also provide a field name. It falls back to the array index only when no valid key is available.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={locales}
|
||||
iterator={(locale) => <LocaleRow />}
|
||||
recordKey="code"
|
||||
/>
|
||||
|
||||
<RecordsIterator
|
||||
data={revisions}
|
||||
recordKey={(revision) => `${revision.documentId}:${revision.version}`}
|
||||
>
|
||||
<RevisionRow />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Record list example
|
||||
description: Render a contextual record list with reusable field components.
|
||||
order: 30
|
||||
toc:
|
||||
- id: define-field-components
|
||||
title: Define field components
|
||||
- id: render-the-list
|
||||
title: Render the list
|
||||
- id: iterator-callback
|
||||
title: Use an iterator callback
|
||||
- id: add-separators
|
||||
title: Add separators
|
||||
---
|
||||
|
||||
## Define field components {#define-field-components}
|
||||
|
||||
Field components depend only on record context, so the same fields work in detail pages, lists, and nested records.
|
||||
|
||||
```tsx
|
||||
function ProductName() {
|
||||
const name = useFieldValue<Product, "name">({ source: "name" })
|
||||
return <strong>{name}</strong>
|
||||
}
|
||||
|
||||
function ProductPrice() {
|
||||
const price = useFieldValue<Product, "price">({ source: "price" })
|
||||
return <span>${price}</span>
|
||||
}
|
||||
```
|
||||
|
||||
## Render the list {#render-the-list}
|
||||
|
||||
Pass shared children to the iterator. It provides a fresh context for every record.
|
||||
|
||||
```tsx
|
||||
<ul>
|
||||
<RecordsIterator data={products}>
|
||||
<li>
|
||||
<ProductName />
|
||||
<ProductPrice />
|
||||
</li>
|
||||
</RecordsIterator>
|
||||
</ul>
|
||||
```
|
||||
|
||||
The iterator returns `null` when `data` is empty or undefined, or when neither children nor iterator is provided. The surrounding data view remains responsible for loading, error, and empty states.
|
||||
|
||||
## Use an iterator callback {#iterator-callback}
|
||||
|
||||
Simple views can use the callback argument directly. The callback is still rendered inside the matching record context, so nested components can keep using the hooks.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
iterator={(product, index) => (
|
||||
<article>
|
||||
<span>#{index + 1}</span>
|
||||
<ProductName />
|
||||
<button onClick={() => addToCart(product.id)}>Add</button>
|
||||
</article>
|
||||
)}
|
||||
/>
|
||||
```
|
||||
|
||||
## Add separators {#add-separators}
|
||||
|
||||
Use `separator` to insert content between adjacent records. It renders neither before the first record nor after the last one. Its argument is the index of the record following the separator, and the separator remains outside that record's `RecordProvider`.
|
||||
|
||||
```tsx
|
||||
<RecordsIterator
|
||||
data={products}
|
||||
separator={(index) => <hr aria-label={`Separator ${index}`} />}
|
||||
>
|
||||
<ProductCard />
|
||||
</RecordsIterator>
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Provide and read records
|
||||
description: Build record-aware components with RecordProvider, useRecord, and useFieldValue.
|
||||
order: 10
|
||||
toc:
|
||||
- id: provide-a-record
|
||||
title: Provide a record
|
||||
- id: read-a-record
|
||||
title: Read a record
|
||||
- id: read-a-field
|
||||
title: Read a field
|
||||
---
|
||||
|
||||
## Provide a record {#provide-a-record}
|
||||
|
||||
Mount the provider at the boundary that owns the data. `RecordProvider` accepts a present, non-nullish record. Use `OptionalRecordProvider` when a query result may be absent.
|
||||
|
||||
```tsx
|
||||
import { RecordProvider } from "@workspace/record"
|
||||
|
||||
function ProductScreen({ product }: { product: Product }) {
|
||||
return (
|
||||
<RecordProvider value={product}>
|
||||
<ProductDetails />
|
||||
</RecordProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { OptionalRecordProvider } from "@workspace/record"
|
||||
|
||||
;<OptionalRecordProvider value={query.data}>
|
||||
<ProductDetails />
|
||||
</OptionalRecordProvider>
|
||||
```
|
||||
|
||||
## Read a record {#read-a-record}
|
||||
|
||||
`useRecord` returns `undefined` when there is no provider or data has not loaded yet.
|
||||
|
||||
```tsx
|
||||
function ProductDetails() {
|
||||
const product = useRecord<Product>()
|
||||
if (!product) return <p>Loading…</p>
|
||||
|
||||
return <h2>{product.name}</h2>
|
||||
}
|
||||
```
|
||||
|
||||
A record-aware component can also accept an optional `record` prop. The explicit record takes precedence over context, making isolated previews and tests straightforward.
|
||||
|
||||
```tsx
|
||||
function ProductTitle({ record }: { record?: Product }) {
|
||||
const product = useRecord({ record })
|
||||
return product ? <h2>{product.name}</h2> : null
|
||||
}
|
||||
```
|
||||
|
||||
## Read a field {#read-a-field}
|
||||
|
||||
`useFieldValue` supports direct properties, nested objects, and numeric array segments. Paths use dot notation.
|
||||
|
||||
```tsx
|
||||
const city = useFieldValue<Customer, "address.city">({
|
||||
source: "address.city",
|
||||
defaultValue: "Unknown",
|
||||
})
|
||||
|
||||
const firstSku = useFieldValue<Order, "items.0.sku">({
|
||||
source: "items.0.sku",
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn how the Record package exposes the current record in data-driven interfaces.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: capabilities
|
||||
title: Capabilities
|
||||
- id: design-boundary
|
||||
title: Design boundary
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/record` is a UI-free React record context layer. A record is not restricted to an object: numbers, booleans, strings, arrays, functions, and plain objects are all valid; only `undefined` and `null` represent absence. Descendants can read the current value without passing a `record` prop through every level.
|
||||
|
||||
## Capabilities {#capabilities}
|
||||
|
||||
- Provide the current record with `RecordProvider`.
|
||||
- Read context with `useRecord`, or override it with an explicit record.
|
||||
- Read nested fields through typed dot paths with `useFieldValue`.
|
||||
- Create an isolated record context for each list item with `RecordsIterator`.
|
||||
- Make components work standalone and inside existing contexts with `OptionalRecordProvider`.
|
||||
|
||||
## Design boundary {#design-boundary}
|
||||
|
||||
This package does not fetch, cache, paginate, manage forms, or render UI. A data layer only needs to pass loaded values to a provider or iterator. `undefined` and `null` mean that no record is available; `0`, `false`, and an empty string remain valid records. Read live form edits from form state instead.
|
||||
Reference in New Issue
Block a user