Skip to main content

reactor-forms

Reactor forms and validation — `UseValidationContext`, built-in validators (`Validate.Required`, `Validate.Email`, `Validate.MinLength`, etc.), `FormField` helper, masked input via `MaskEngine`, `InputFormatter`. Use when building data-entry screens, validation flows, or controlled-input forms.

معلومات المصدر

المستودع
microsoft/microsoft-ui-reactor
آخر نشاط في المصدر
٢٨ سبتمبر ٢٠٢٦ في ١٨:٠١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٧٤
التفرعات
٥٨

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
reactor-forms
description
Reactor forms and validation — `UseValidationContext`, built-in validators (`Validate.Required`, `Validate.Email`, `Validate.MinLength`, etc.), `FormField` helper, masked input via `MaskEngine`, `InputFormatter`. Use when building data-entry screens, validation flows, or controlled-input forms.
# Forms and Validation in Reactor Reactor forms use a **controlled-input pattern** — every input has an explicit `(value, setter)` pair driven by `UseState`. There is no two-way binding. Validation is layered on top via `UseValidationContext` and declarative `.Validate()` modifiers. > **Controlled prop note:** factory call sites keep the plain `(value, > setter)` shape, but the underlying element records use `Optional<T>`. > Read element props with `.Value` / `.GetValueOrDefault(...)`; use > `Optional<T>.Unset` only when the WinUI control should own the value. See > [`migration/050-optional-t.md`](../../../../docs/guide/migration/050-optional-t.md). ## Quick reference | API | Purpose | |-----|---------| | `TextBox(value, setValue)` | Controlled text input | | `UseValidationContext()` | Track validation messages, touched/dirty state | | `.Validate(...)` | Attach built-in validators to an input | | `FormField(input, label: ...)` | Wraps input with label, error display, required marker | | `new MaskEngine(...)` | Masked text input (phone, SSN, etc.) | | `InputFormatter.Currency(...)` | Format-as-you-type | ## 1. Controlled inputs Every input takes `(value, setter)`. State lives in the component: ```csharp var (name, setName) = UseState(""); var (age, setAge) = UseState(0); var (agreed, setAgreed) = UseState(false); return VStack(12, TextBox(name, setName, placeholderText: "Name"), NumberBox(age, setAge), CheckBox(agreed, setAgreed, label: "I agree"), Button("Submit", onSubmit).IsEnabled(!(string.IsNullOrEmpty(name) || !agreed)) ); ``` ### Available input types | Factory | Value type | Common modifiers | |---------|-----------|------------------| | `TextBox(value, setValue, placeholderText, header)` | `string` | `.Header()`, `.IsReadOnly()`, `.AcceptsReturn()`, `.TextWrapping()`, `.MaxLength(n)`, `.NumericInput()`, `.EmailInput()`, `.Changed(handler)` | | `PasswordBox(password, setPassword, placeholderText)` | `string` | `.Header(text)`, `.MaxLength(n)`, `.PasswordChanged(handler)` | | `NumberBox(value, setValue, header)` | `double` | `.PlaceholderText(text)`, `.Range(min, max)`, `.SpinButtons(...)` | | `Slider(value, min, max, setValue)` | `double` | `.Header()`, `.StepFrequency()` | | `ToggleSwitch(isOn, setIsOn, header, onContent, offContent)` | `bool` | `.Header()` | | `CheckBox(isChecked, setIsChecked, label)` | `bool` | — | | `RadioButtons(items, selected, setSelected, header)` | `int` | `.Set(rb => ...)` | | `ComboBox(items, selected, setSelected)` | `object` | `.Header()`, `.IsEditable()`, `.PlaceholderText(text)` | | `DatePicker(date, setDate, header)` | `DateTimeOffset` | `.Set(dp => ...)` | | `TimePicker(time, setTime, header)` | `TimeSpan` | `.Set(tp => ...)` | | `AutoSuggestBox(text, setText, onQuerySubmitted)` | `string` | `.PlaceholderText(text)`, `.Set(asb => ...)` | | `RichEditBox(doc, setDoc, header)` | `string` | `.PlaceholderText(text)`, `.Set(reb => ...)` | | `CalendarDatePicker(date, setDate)` | `DateTimeOffset?` | `.PlaceholderText(text)`, `.Set(cdp => ...)` | **Named-input shapes** (Spec 039 §17.3): `.NumericInput()` / `.EmailInput()` preconfigure `InputScope` and IME hints so on-screen / soft keyboards open in the right mode. Stack them with validators: ```csharp TextBox(email, setEmail, placeholderText: "you@example.com") .EmailInput() .MaxLength(254) .Validate("email", email, Validate.Required(), Validate.Email()) ``` For modifiers that aren't in the typed surface (e.g. `PasswordRevealMode`, date min/max), use `.Set(native => ...)` to reach the underlying WinUI control. The full catalog is in `references/reactor.api.txt`. ## 2. Simple validation (derived booleans) For trivial forms, derive validation from state: ```csharp var (email, setEmail) = UseState(""); var isValid = email.Contains('@') && email.Length > 3; return VStack(12, TextBox(email, setEmail, placeholderText: "Email"), Button("Submit", onSubmit).IsEnabled(isValid) ); ``` This is fine for 1–2 fields. For anything more, use `UseValidationContext`. ## 3. UseValidationContext Tracks per-field validation messages, touched/dirty state, and overall form validity: ```csharp var validation = this.UseValidationContext(); var (name, setName) = UseState(""); var (email, setEmail) = UseState(""); return VStack(12, TextBox(name, setName, placeholderText: "Name") .Validate("name", name, Validate.Required("Name is required"), Validate.MinLength(2, "Name too short")), TextBox(email, setEmail, placeholderText: "Email") .Validate("email", email, Validate.Required("Email is required"), Validate.Email("Invalid email")), Button("Submit", () => { validation.MarkAllTouched(); if (validation.IsValid()) Submit(name, email); }) ); ``` `.Validate(fieldName, value, ...)` resolves the surrounding `ValidationContext` through React-style ambient context — you do not pass `validation` explicitly. Passing the current value (the second arg) runs the validators right there, during the render, so a `When(validation.HasError("email"), ...)` placed *after* the field reads the result in the same pass rather than one render late. The validator-only overload `.Validate(fieldName, ...)` has no value to check, so it only records the validators: nothing runs them, and on a bare control nothing registers the field either. Inside a `FormField` the field is registered on mount, so `MarkAllTouched()` covers it. You do not need `.Provide(ValidationContexts.Current, validation)`: a component-local context is published to the rendered subtree automatically, so `FormField`, `ValidationVisualizer`, and nested components find it. Providing one explicitly is what *descendants* resolve, but it does not redirect the providing component's own `.Validate()` calls — those already ran while the tree was being built. To pool several components' fields into one context, provide it from a parent and let each child call `UseValidationContext()`. Mutating the context re-renders the component that created it. That is why `MarkAllTouched()` alone reveals the errors on a failed submit, even though no component state changed. ### ValidationContext API | Member | Purpose | |--------|---------| | `.IsValid()` | `true` when no field has Error-severity messages | | `.IsDirty()` | `true` when any registered field differs from initial value | | `.IsDirty("field")` | Per-field dirty check | | `.MarkAllTouched()` | Mark every registered field touched (typical on submit) | | `.MarkTouched("field")` | Mark a single field touched | | `.Reset("field")` | Reset one field to initial value, returns the initial | | `.ResetAll()` | Reset all fields to initial values | | `.ClearAll()` | Clear all messages (preserve touched/initial state) | | `.GetMessages("field")` | Get error messages for a specific field | | `.IsTouched("field")` | Whether the user has interacted with a field | | `.Changed` | Event raised when the context's observable state changes | ## 4. Built-in validators The `.Validate()` modifier accepts an array of validators: | Validator | Purpose | |-----------|---------| | `Validate.Required(msg)` | Non-empty | | `Validate.MinLength(n, msg)` | Minimum string length | | `Validate.MaxLength(n, msg)` | Maximum string length | | `Validate.Email(msg)` | Email format | | `Validate.Match(pattern, msg)` | Custom regex pattern | | `Validate.Range(min, max, msg)` | Numeric range | | `Validate.Must<T>(predicate, msg)` | Arbitrary predicate | | `Validate.EqualTo<T>(value, msg)` | Fields must match (confirm password) | | `Validate.Url(msg)` | URL format | | `Validate.MustBeTrue(msg)` | Boolean must be true (checkboxes) | ## 5. FormField helper `FormField` wraps an input with a label, required marker, description text, and error display: ```csharp var validation = this.UseValidationContext(); var (name, setName) = UseState(""); return FormField( TextBox(name, setName, placeholderText: "Enter your name") .Validate("name", name, Validate.Required("Required")), label: "Full Name", required: true, description: "As it appears on your ID", showWhen: ShowWhen.WhenTouched // or Always, WhenDirty, AfterFirstSubmit, Never ); ``` `ShowWhen` controls when error messages appear: - `WhenTouched` — after the user has interacted with the field (recommended default). `FormField` marks its own field touched on blur; elsewhere call `MarkTouched`. - `Always` — immediately, even before user interaction - `WhenDirty` — only after the value has changed from its baseline. Requires `SetInitialValue(field, value)`: with no baseline recorded a field is never dirty, so this policy stays silent forever. - `AfterFirstSubmit` — only after the first submit attempt, which is `MarkAllTouched()`. `ResetAll()` clears it, so the next reveal waits for a fresh submit. The verdict itself is unaffected by any of these — `IsValid()` and `GetMessages()` are current from the first render. `ShowWhen` only gates display. ## 6. Masked input `MaskEngine` restricts and formats input as the user types: ```csharp var mask = UseMemo(() => new MaskEngine(MaskPreset.PhoneUS)); var (phone, setPhone) = UseState(""); return TextBox(phone, v => setPhone(mask.Apply(v)), placeholderText: "(555) 555-0123"); ``` ### Mask presets | Preset | Format | |--------|--------| | `MaskPreset.PhoneUS` | `(___) ___-____` | | `MaskPreset.SSN` | `___-__-____` | | `MaskPreset.ZipCode` | `_____` | | `MaskPreset.ZipCodePlus4` | `_____-____` | | `MaskPreset.CreditCard` | `____ ____ ____ ____` | | `MaskPreset.Date` | `__/__/____` | Custom masks: `new MaskEngine("AA-####")` where `A` = letter, `#` = digit, `*` = any. ## 7. Input formatters `InputFormatter` applies format-as-you-type transformations: ```csharp var (amount, setAmount) = UseState(""); return TextBox(amount, v => setAmount(InputFormatter.Currency(symbol: "$").Format(v)), placeholderText: "$0.00"); ``` | Formatter | Effect | |----------|--------| | `InputFormatter.Currency(symbol: "$")` | `$1,234.56` | | `InputFormatter.PhoneUS` | `(555) 555-0123` |
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub