|
| 1 | +# Building UIs With Svelte |
| 2 | + |
| 3 | +This guide describes how to use the `tinybase/ui-svelte` module to build |
| 4 | +reactive user interfaces in Svelte 5 applications. |
| 5 | + |
| 6 | +## Installation |
| 7 | + |
| 8 | +Install TinyBase and Svelte together: |
| 9 | + |
| 10 | +```bash |
| 11 | +npm install tinybase svelte |
| 12 | +``` |
| 13 | + |
| 14 | +Then import hooks and components directly from the `tinybase/ui-svelte` module |
| 15 | +in your component's `<script>` block. |
| 16 | + |
| 17 | +## Reactive Hooks |
| 18 | + |
| 19 | +Every hook in the `ui-svelte` module returns a reactive object with a `current` |
| 20 | +property. Because `current` is backed by Svelte's `$state` rune, any part of |
| 21 | +your template that reads it will automatically update when the underlying Store |
| 22 | +data changes. |
| 23 | + |
| 24 | +Here is the `useCell` hook reading the color of a pet and displaying it in a |
| 25 | +paragraph: |
| 26 | + |
| 27 | +```svelte |
| 28 | +<script> |
| 29 | + import {createStore} from 'tinybase'; |
| 30 | + import {useCell} from 'tinybase/ui-svelte'; |
| 31 | +
|
| 32 | + const store = createStore().setCell('pets', 'fido', 'color', 'brown'); |
| 33 | + const color = useCell('pets', 'fido', 'color', store); |
| 34 | +</script> |
| 35 | +
|
| 36 | +<p>Color: {color.current}</p> |
| 37 | +<!-- Renders: <p>Color: brown</p> --> |
| 38 | +``` |
| 39 | + |
| 40 | +When `store.setCell('pets', 'fido', 'color', 'walnut')` is called anywhere, the |
| 41 | +paragraph immediately re-renders to show `walnut`. No manual subscriptions, no |
| 42 | +`$:` labels, no `onDestroy` cleanup — the hook registers and removes TinyBase |
| 43 | +listeners automatically using Svelte's `$effect` lifecycle. |
| 44 | + |
| 45 | +There are hooks corresponding to every Store reading method: |
| 46 | + |
| 47 | +- `useValues` — reactive equivalent of `getValues` |
| 48 | +- `useValueIds` — reactive equivalent of `getValueIds` |
| 49 | +- `useValue` — reactive equivalent of `getValue` |
| 50 | +- `useHasValues` — reactive equivalent of `hasValues` |
| 51 | +- `useTables` — reactive equivalent of `getTables` |
| 52 | +- `useTableIds` — reactive equivalent of `getTableIds` |
| 53 | +- `useTable` — reactive equivalent of `getTable` |
| 54 | +- `useRowIds` — reactive equivalent of `getRowIds` |
| 55 | +- `useSortedRowIds` — reactive equivalent of `getSortedRowIds` |
| 56 | +- `useRow` — reactive equivalent of `getRow` |
| 57 | +- `useCellIds` — reactive equivalent of `getCellIds` |
| 58 | +- `useCell` — reactive equivalent of `getCell` |
| 59 | + |
| 60 | +There are also hooks for the higher-level TinyBase objects: `useMetric`, |
| 61 | +`useMetricIds`, `useSliceIds`, `useSliceRowIds`, `useResultCell`, |
| 62 | +`useResultRow`, `useResultTable`, `useResultRowIds`, `useCheckpointIds`, |
| 63 | +`useCheckpoint`, and more. |
| 64 | + |
| 65 | +## Reactive Parameters With R |
| 66 | + |
| 67 | +All hook parameters accept either a plain value or a reactive getter function. |
| 68 | +This is the `R<T>` type: `T | (() => T)`. |
| 69 | + |
| 70 | +Passing a getter function that reads a `$state` variable makes the hook |
| 71 | +reactively track which data it fetches. In this example, `rowId` is a prop and |
| 72 | +the hook refetches automatically when it changes: |
| 73 | + |
| 74 | +```svelte |
| 75 | +<script> |
| 76 | + import {useCell} from 'tinybase/ui-svelte'; |
| 77 | +
|
| 78 | + let {rowId, store} = $props(); |
| 79 | + const color = useCell('pets', () => rowId, 'color', store); |
| 80 | +</script> |
| 81 | +
|
| 82 | +<p>{color.current}</p> |
| 83 | +``` |
| 84 | + |
| 85 | +Without the `() => rowId` wrapper, changing the `rowId` prop would not cause |
| 86 | +the hook to re-read the Store for the new row. |
| 87 | + |
| 88 | +## Writable State With useCellState |
| 89 | + |
| 90 | +The `useCellState` and `useValueState` hooks expose a writable `current` |
| 91 | +property. Writing to it calls `store.setCell()` or `store.setValue()`. This |
| 92 | +makes Svelte's `bind:value` directive work for two-way binding: |
| 93 | + |
| 94 | +```svelte |
| 95 | +<script> |
| 96 | + import {createStore} from 'tinybase'; |
| 97 | + import {useCellState} from 'tinybase/ui-svelte'; |
| 98 | +
|
| 99 | + const store = createStore().setCell('pets', 'fido', 'color', 'brown'); |
| 100 | + const color = useCellState('pets', 'fido', 'color', store); |
| 101 | +</script> |
| 102 | +
|
| 103 | +<input bind:value={color.current} /> |
| 104 | +<p>Current color: {color.current}</p> |
| 105 | +``` |
| 106 | + |
| 107 | +Typing in the `<input>` updates the Store, which is immediately reflected |
| 108 | +in the paragraph — without any additional event handling. |
| 109 | + |
| 110 | +## Context With Provider |
| 111 | + |
| 112 | +For larger apps, passing the `store` object to every hook as the last argument |
| 113 | +gets repetitive. The `Provider` component sets a context that all descendant |
| 114 | +hooks use automatically when no explicit reference is given: |
| 115 | + |
| 116 | +```svelte |
| 117 | +<!-- App.svelte --> |
| 118 | +<script> |
| 119 | + import {createStore} from 'tinybase'; |
| 120 | + import {Provider} from 'tinybase/ui-svelte'; |
| 121 | + import Pane from './Pane.svelte'; |
| 122 | +
|
| 123 | + const store = createStore().setTables({ |
| 124 | + pets: {fido: {species: 'dog', color: 'brown'}}, |
| 125 | + }); |
| 126 | +</script> |
| 127 | +
|
| 128 | +<Provider {store}> |
| 129 | + <Pane /> |
| 130 | +</Provider> |
| 131 | +``` |
| 132 | + |
| 133 | +```svelte |
| 134 | +<!-- Pane.svelte --> |
| 135 | +<script> |
| 136 | + import {useCell} from 'tinybase/ui-svelte'; |
| 137 | +
|
| 138 | + // No store argument — resolved automatically from the nearest Provider |
| 139 | + const species = useCell('pets', 'fido', 'species'); |
| 140 | + const color = useCell('pets', 'fido', 'color'); |
| 141 | +</script> |
| 142 | +
|
| 143 | +<p>{species.current} ({color.current})</p> |
| 144 | +``` |
| 145 | + |
| 146 | +`Provider` accepts `store`, `storesById`, `metrics`, `metricsById`, and |
| 147 | +equivalent props for every TinyBase object type. When multiple stores are |
| 148 | +provided, hooks reference them by Id: |
| 149 | + |
| 150 | +```svelte |
| 151 | +<script> |
| 152 | + import {useCell} from 'tinybase/ui-svelte'; |
| 153 | +
|
| 154 | + const color = useCell('pets', 'fido', 'color', 'petStore'); |
| 155 | +</script> |
| 156 | +``` |
| 157 | + |
| 158 | +Descendant components can register additional objects into the nearest Provider |
| 159 | +context at runtime using `provideStore`, `provideMetrics`, and similar |
| 160 | +functions. |
| 161 | + |
| 162 | +## View Components |
| 163 | + |
| 164 | +For common rendering tasks, the module provides pre-built view components that |
| 165 | +wrap the hooks and render data directly. These make it easy to compose UIs from |
| 166 | +Store data: |
| 167 | + |
| 168 | +```svelte |
| 169 | +<script> |
| 170 | + import {createStore} from 'tinybase'; |
| 171 | + import {CellView, RowView, TablesView} from 'tinybase/ui-svelte'; |
| 172 | +
|
| 173 | + const store = createStore().setTables({ |
| 174 | + pets: { |
| 175 | + fido: {species: 'dog', color: 'brown'}, |
| 176 | + felix: {species: 'cat', color: 'black'}, |
| 177 | + }, |
| 178 | + }); |
| 179 | +</script> |
| 180 | +
|
| 181 | +<!-- Renders the value of a single cell --> |
| 182 | +<CellView tableId="pets" rowId="fido" cellId="species" {store} /> |
| 183 | +
|
| 184 | +<!-- Renders all cells in a row concatenated --> |
| 185 | +<RowView tableId="pets" rowId="fido" {store} /> |
| 186 | +
|
| 187 | +<!-- Renders the entire tables structure --> |
| 188 | +<TablesView {store} /> |
| 189 | +``` |
| 190 | + |
| 191 | +Components accept a `cellComponent` or `rowComponent` snippet prop to customize |
| 192 | +how individual cells or rows are rendered: |
| 193 | + |
| 194 | +```svelte |
| 195 | +<script> |
| 196 | + import {RowView} from 'tinybase/ui-svelte'; |
| 197 | +</script> |
| 198 | +
|
| 199 | +<RowView tableId="pets" rowId="fido" {store}> |
| 200 | + {#snippet cellComponent(tableId, rowId, cellId)} |
| 201 | + <span class="cell">{cellId}</span> |
| 202 | + {/snippet} |
| 203 | +</RowView> |
| 204 | +``` |
| 205 | + |
| 206 | +The full set of view components covers every level of the Store hierarchy and |
| 207 | +the higher-level TinyBase objects: |
| 208 | + |
| 209 | +- `CellView`, `RowView`, `TableView`, `TablesView` |
| 210 | +- `ValueView`, `ValuesView` |
| 211 | +- `SliceView`, `IndexView` |
| 212 | +- `ResultCellView`, `ResultRowView`, `ResultTableView`, `ResultSortedTableView` |
| 213 | +- `SortedTableView` |
| 214 | +- `MetricView` |
| 215 | +- `CheckpointView`, `BackwardCheckpointsView`, `CurrentCheckpointView`, |
| 216 | + `ForwardCheckpointsView` |
| 217 | +- `RemoteRowView`, `LocalRowsView`, `LinkedRowsView` |
| 218 | + |
| 219 | +That completes the overview of the `ui-svelte` module. For API reference, see |
| 220 | +the [ui-svelte](/api/ui-svelte/) documentation. |
0 commit comments