Skip to content

Commit f337950

Browse files
committed
docs: add TSRX + Octane docs
1 parent 47014f7 commit f337950

16 files changed

Lines changed: 3472 additions & 0 deletions

‎docs/config.json‎

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@
5454
}
5555
]
5656
},
57+
{
58+
"label": "octane",
59+
"children": [
60+
{
61+
"label": "Quick Start",
62+
"to": "framework/octane/quick-start"
63+
}
64+
]
65+
},
5766
{
5867
"label": "vue",
5968
"children": [
@@ -247,6 +256,71 @@
247256
}
248257
]
249258
},
259+
{
260+
"label": "octane",
261+
"children": [
262+
{
263+
"label": "Basic Concepts",
264+
"to": "framework/octane/guides/basic-concepts"
265+
},
266+
{
267+
"label": "Form Validation",
268+
"to": "framework/octane/guides/validation"
269+
},
270+
{
271+
"label": "Dynamic Validation",
272+
"to": "framework/octane/guides/dynamic-validation"
273+
},
274+
{
275+
"label": "Async Initial Values",
276+
"to": "framework/octane/guides/async-initial-values"
277+
},
278+
{
279+
"label": "Arrays",
280+
"to": "framework/octane/guides/arrays"
281+
},
282+
{
283+
"label": "Form Groups",
284+
"to": "framework/octane/guides/form-groups"
285+
},
286+
{
287+
"label": "Linked Fields",
288+
"to": "framework/octane/guides/linked-fields"
289+
},
290+
{
291+
"label": "Reactivity",
292+
"to": "framework/octane/guides/reactivity"
293+
},
294+
{
295+
"label": "Listeners",
296+
"to": "framework/octane/guides/listeners"
297+
},
298+
{
299+
"label": "Custom Errors",
300+
"to": "framework/octane/guides/custom-errors"
301+
},
302+
{
303+
"label": "Submission Handling",
304+
"to": "framework/octane/guides/submission-handling"
305+
},
306+
{
307+
"label": "UI Libraries",
308+
"to": "framework/octane/guides/ui-libraries"
309+
},
310+
{
311+
"label": "Focus Management",
312+
"to": "framework/octane/guides/focus-management"
313+
},
314+
{
315+
"label": "Form Composition",
316+
"to": "framework/octane/guides/form-composition"
317+
},
318+
{
319+
"label": "Debugging",
320+
"to": "framework/octane/guides/debugging"
321+
}
322+
]
323+
},
250324
{
251325
"label": "vue",
252326
"children": [
@@ -555,6 +629,111 @@
555629
}
556630
]
557631
},
632+
{
633+
"label": "octane",
634+
"children": [
635+
{
636+
"label": "Octane Reference",
637+
"to": "framework/octane/reference/index"
638+
},
639+
{
640+
"label": "Variables / Field",
641+
"to": "framework/octane/reference/variables/Field"
642+
},
643+
{
644+
"label": "Variables / FormGroup",
645+
"to": "framework/octane/reference/variables/FormGroup"
646+
},
647+
{
648+
"label": "Variables / useIsomorphicLayoutEffect",
649+
"to": "framework/octane/reference/variables/useIsomorphicLayoutEffect"
650+
},
651+
{
652+
"label": "Functions / createFormHook",
653+
"to": "framework/octane/reference/functions/createFormHook"
654+
},
655+
{
656+
"label": "Functions / createFormHookContexts",
657+
"to": "framework/octane/reference/functions/createFormHookContexts"
658+
},
659+
{
660+
"label": "Functions / useField",
661+
"to": "framework/octane/reference/functions/useField"
662+
},
663+
{
664+
"label": "Functions / useFieldGroup",
665+
"to": "framework/octane/reference/functions/useFieldGroup"
666+
},
667+
{
668+
"label": "Functions / useForm",
669+
"to": "framework/octane/reference/functions/useForm"
670+
},
671+
{
672+
"label": "Functions / useFormGroup",
673+
"to": "framework/octane/reference/functions/useFormGroup"
674+
},
675+
{
676+
"label": "Functions / useSelector",
677+
"to": "framework/octane/reference/functions/useSelector"
678+
},
679+
{
680+
"label": "Interfaces / CreateFormHookReturn",
681+
"to": "framework/octane/reference/interfaces/CreateFormHookReturn"
682+
},
683+
{
684+
"label": "Interfaces / OctaneFormApi",
685+
"to": "framework/octane/reference/interfaces/OctaneFormApi"
686+
},
687+
{
688+
"label": "Interfaces / SubscribeComponent",
689+
"to": "framework/octane/reference/interfaces/SubscribeComponent"
690+
},
691+
{
692+
"label": "Interfaces / UseFieldOptions",
693+
"to": "framework/octane/reference/interfaces/UseFieldOptions"
694+
},
695+
{
696+
"label": "Interfaces / UseFieldOptionsBound",
697+
"to": "framework/octane/reference/interfaces/UseFieldOptionsBound"
698+
},
699+
{
700+
"label": "Interfaces / WithFieldGroupProps",
701+
"to": "framework/octane/reference/interfaces/WithFieldGroupProps"
702+
},
703+
{
704+
"label": "Interfaces / WithFormProps",
705+
"to": "framework/octane/reference/interfaces/WithFormProps"
706+
},
707+
{
708+
"label": "Types / FieldComponent",
709+
"to": "framework/octane/reference/type-aliases/FieldComponent"
710+
},
711+
{
712+
"label": "Types / FormGroupComponent",
713+
"to": "framework/octane/reference/type-aliases/FormGroupComponent"
714+
},
715+
{
716+
"label": "Types / LensFieldComponent",
717+
"to": "framework/octane/reference/type-aliases/LensFieldComponent"
718+
},
719+
{
720+
"label": "Types / OctaneFormExtendedApi",
721+
"to": "framework/octane/reference/type-aliases/OctaneFormExtendedApi"
722+
},
723+
{
724+
"label": "Types / ServerFormState",
725+
"to": "framework/octane/reference/type-aliases/ServerFormState"
726+
},
727+
{
728+
"label": "Types / UseField",
729+
"to": "framework/octane/reference/type-aliases/UseField"
730+
},
731+
{
732+
"label": "Types / UseFormGroup",
733+
"to": "framework/octane/reference/type-aliases/UseFormGroup"
734+
}
735+
]
736+
},
558737
{
559738
"label": "vue",
560739
"children": [
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
id: arrays
3+
title: Arrays
4+
---
5+
6+
TanStack Form supports arrays as form values, including objects and nested fields inside an array. In TSRX, render array fields with a keyed `@for` loop.
7+
8+
## Basic Usage
9+
10+
Use `mode="array"` to manage the array and read its entries through `field.state.value`. Give an initially empty array a type so that field paths and new entries can be inferred:
11+
12+
```tsrx
13+
import { useForm } from '@tanstack/octane-form'
14+
15+
type Person = {
16+
id: string;
17+
name: string;
18+
age: number;
19+
}
20+
21+
function App() @{
22+
const form = useForm({
23+
defaultValues: {
24+
people: [] as Person[],
25+
},
26+
})
27+
28+
<form.Field
29+
name="people"
30+
mode="array"
31+
children={(field) => <div>
32+
@for (const person of field.state.value; index i; key person.id) {
33+
<p>Person {i + 1}: {person.id}</p>
34+
}
35+
</div>}
36+
/>
37+
}
38+
```
39+
40+
Call `pushValue` to add an entry. The array field updates and the loop renders the new row:
41+
42+
```tsrx
43+
<button
44+
onClick={() => field.pushValue({
45+
id: crypto.randomUUID(),
46+
name: '',
47+
age: 0,
48+
})}
49+
type="button"
50+
>
51+
Add person
52+
</button>
53+
```
54+
55+
Access an entry's nested fields using an indexed path:
56+
57+
```tsrx
58+
<form.Field
59+
name={`people[${i}].name`}
60+
children={(subField) =>
61+
<input
62+
value={subField.state.value}
63+
onInput={(event) => subField.handleChange(event.currentTarget.value)}
64+
onBlur={subField.handleBlur}
65+
/>}
66+
/>
67+
```
68+
69+
The loop's `key` identifies the row, while the field's `name` identifies its current position in the form. Use a stable key when rows can be removed or reordered, so Octane can preserve each row's DOM and component state. Native text inputs use `onInput` to update on each edit.
70+
71+
## Full Example
72+
73+
Save components that render templates in `.tsrx` files:
74+
75+
```tsrx
76+
// src/App.tsrx
77+
import { useForm } from '@tanstack/octane-form'
78+
79+
type Person = {
80+
id: string;
81+
name: string;
82+
age: number;
83+
}
84+
85+
export function App() @{
86+
const form = useForm({
87+
defaultValues: {
88+
people: [] as Person[],
89+
},
90+
onSubmit: ({ value }) => {
91+
alert(JSON.stringify(value))
92+
},
93+
})
94+
95+
<form
96+
onSubmit={(event) => {
97+
event.preventDefault()
98+
event.stopPropagation()
99+
void form.handleSubmit()
100+
}}
101+
>
102+
<form.Field
103+
name="people"
104+
mode="array"
105+
children={(field) => <div>
106+
@for (const person of field.state.value; index i; key person.id) {
107+
<div>
108+
<form.Field
109+
name={`people[${i}].name`}
110+
children={(subField) => <label>
111+
<span>Name for person {i + 1}</span>
112+
<input
113+
value={subField.state.value}
114+
onInput={(event) => subField.handleChange(
115+
event.currentTarget.value,
116+
)}
117+
onBlur={subField.handleBlur}
118+
/>
119+
</label>}
120+
/>
121+
<button type="button" onClick={() => field.removeValue(i)}>
122+
Remove person
123+
</button>
124+
</div>
125+
}
126+
<button
127+
type="button"
128+
onClick={() => field.pushValue({
129+
id: crypto.randomUUID(),
130+
name: '',
131+
age: 0,
132+
})}
133+
>
134+
Add person
135+
</button>
136+
</div>}
137+
/>
138+
<form.Subscribe
139+
selector={(state) => [state.canSubmit, state.isSubmitting]}
140+
children={([canSubmit, isSubmitting]) => <button
141+
type="submit"
142+
disabled={!canSubmit}
143+
>
144+
@if (isSubmitting) {
145+
<>...</>
146+
} @else {
147+
<>Submit</>
148+
}
149+
</button>}
150+
/>
151+
</form>
152+
}
153+
```

0 commit comments

Comments
 (0)