- name
- reactor-getting-started
- description
- Reactor essentials in one place — React-to-Reactor mental model, minimal app shape, hooks, the most-used factories, the critical gotchas, and project setup. This is the only skill you need loaded for typical Reactor work; load topical skills (`reactor-async`, `reactor-design`, etc.) only when the task explicitly calls for them.
## Coming from React? Read this first.
**Reactor concepts are React's, with one C# spelling.** If you know React, you already know how Reactor works — components render an element tree, hooks manage state and effects, lists need keys, lifting state up is the same. Trust your React intuition for *shape*; verify the *names* against the table below or `references/reactor.api.txt`.
| React | Reactor (C#) |
|---|---|
| `function App() { … }` | `class App : Component { override Element Render() { … } }` |
| `useState(0)` | `var (count, setCount) = UseState(0);` |
| `useReducer(reduce, init)` | `var (state, dispatch) = UseReducer<TState,TAction>(reduce, init);` |
| `useEffect(fn, [dep])` | `UseEffect(fn, dep);` |
| `useMemo(() => v, [dep])` | `UseMemo(() => v, dep)` |
| `useCallback(fn, [dep])` | `UseCallback(fn, dep)` |
| `useRef(v)` | `UseRef(v)` |
| callback ref / `ref={node => …}` | `UseElementRef<T>()` + `.Ref(r)` |
| `useContext(Ctx)` | `UseContext(Ctx)` |
| `<Provider value={x}>{c}</Provider>` | `c.Provide(Ctx, x)` |
| `<div>` (linear layout) | `FlexColumn(...)` / `FlexRow(...)` (CSS-flexbox semantics) |
| `<div>` (shrink-wrap) | `VStack(...)` / `HStack(...)` |
| `<span>text</span>` | `TextBlock("text")` |
| `<h1>` / `<h2>` / small caption | `Heading(...)` / `SubHeading(...)` / `Caption(...)` |
| `<button onClick={fn}>` | `Button("label", fn)` |
| `<input value={v} onChange={e=>…}>` | `TextBox(v, setV)` |
| `<select>` | `ComboBox(items, index, setIndex)` |
| `<input type="checkbox">` | `CheckBox(checked, setChecked)` |
| `{cond && <X/>}` | `cond ? X() : null` (null children are filtered) |
| `{a ? <X/> : <Y/>}` | `a ? X() : Y()` |
| `{items.map(i => <Card key={i.id} … />)}` | `items.Select(i => Component<Card,…>(…).WithKey(i.id)).ToArray()` |
| `<Card {...props} />` | `Component<Card, CardProps>(new CardProps(...))` |
| `key={i.id}` | `.WithKey(i.id)` |
| `className="..."` (style) | `.Background(...)`, `.Padding(...)`, `.Margin(...)`, etc. |
| `style={{margin: 10}}` | `.Margin(10)` |
| `display: flex; flex: 1` | `FlexRow(...)` / `.Flex(grow: 1, basis: 0)` |
| `gap: 8` | `VStack(8, …)`, `FlexRow(...) with { ColumnGap = 8 }` (FlexColumn → `RowGap`) |
| React Query `useQuery` / `useMutation` | `UseResource` / `UseMutation` (see `reactor-async`) |
| JSX | C# method calls + `using static Microsoft.UI.Reactor.Factories` |
**One important difference:** in Reactor, `UseState` with a `List<T>` won't re-render on `.Add()` — same reference. Use `UseReducer` for lists (just like `useReducer` is preferred in React for complex state).
> **Controlled prop note:** the factories shown here still take plain values
> (`TextBox(text, setText)`, `Slider(value, ...)`) and wrap them for you.
> Element-record properties behind those factories use `Optional<T>` so
> `Optional<T>.Unset` can mean "the control owns this value". See
> [`migration/050-optional-t.md`](../../../../docs/guide/migration/050-optional-t.md).
## Starting a new app
`dotnet new reactor -n <Name>` scaffolds the canonical shape: `App.cs` (entry point + initial component, with the `using` block at the top) plus `<Name>.csproj`. Richer shells: `reactor-mvu`, `reactor-navview`, `reactor-tabview`. See the anti-probe + `mur check` notes under "Use a `.csproj` …" below for what comes out of the scaffold.
For a single-file `dotnet run App.cs` demo (no `.csproj`), prepend the file-level `#:package` / `#:property` headers — see `reactor-build-and-check`'s single-file-scripts section.
## Use a `.csproj` when you need …
… multiple files, **analyzers** (single-file `.cs` builds don't load them), or shared project references. `dotnet new reactor` scaffolds the canonical csproj — you don't need to author one from scratch.
`UseWinUI` MUST be `true`. **No XAML files of any kind.**
**Don't change the packaging mode of a project you didn't scaffold — keep whatever the scaffold
produced.** Reactor works in both shapes:
- **Unpackaged** — `<WindowsPackageType>None</WindowsPackageType>`, no `Package.appxmanifest`.
`None` wins over `EnableMsixTooling`, so a project carrying both still builds unpackaged. Runs
from any folder, no MSIX registration. Scaffold with `dotnet new reactor` and set this property.
- **Packaged** — has a `Package.appxmanifest`. Either omit `WindowsPackageType` entirely (the
default produces a packaged app) or set it to `MSIX`; pair it with `EnableMsixTooling=true` for
the single-project MSIX tooling. Launches with package identity, which is what identity-gated
APIs require. This is what the `dotnet new reactor` family produces.
Neither is "the" Reactor shape, and neither is signalled by one property alone. **If a project has a
`Package.appxmanifest`, it is packaged on purpose — adding
`<WindowsPackageType>None</WindowsPackageType>` to it silently strips its identity, and the build
still succeeds, so nothing surfaces the mistake.** Switch modes only when the user asks, and switch
the whole property *set*, not a single flag — the `packaging` guide has both shapes in full,
including the explicit-`MSIX` form.
**After `dotnet new reactor -n <Name>`, the source you edit is `App.cs` (entry point + initial component) next to `<Name>.csproj`.** The packaged templates also scaffold MSIX packaging inputs — `Package.appxmanifest`, `app.manifest`, `Assets/`, `Properties/launchSettings.json`, `Properties/PublishProfiles/`; those are not source — leave them alone. There is no `Program.cs` and no `GlobalUsings.cs` — modify `App.cs` in place. `App.cs` has its own `using` directives at the top — see the *Required imports* section below — and that is where you add new namespaces (e.g. `using System.Linq;` when you reach for `.Select(...)`). Some templates enable implicit usings, so a namespace may already be in scope. Don't probe the `.csproj` after scaffolding unless you're adding a `PackageReference` or changing a property — `Restore succeeded.` in the scaffold stdout is the only confirmation you need.
**Scaffolded apps are packaged, so `dotnet run` launches them with MSIX package identity** — the F5 equivalent. That needs **Developer Mode on** (Settings → System → For developers) to register the loose-layout package, and a concrete architecture (the template declares `x86;x64;ARM64`; AnyCPU is rejected).
**The scaffolded csproj ships a Debug-only ItemGroup that adds `Microsoft.UI.Reactor.Devtools` + `Reactor.DevtoolsSupport=true`.** `Properties/launchSettings.json` carries three profiles: a default **Package** profile, an **Unpackaged** profile, and an **Unpackaged, Devtools** profile that passes `--devtools` to bring up the devtools menu. Pick the devtools profile in the launch dropdown when you want it. Release builds drop the devtools package and host-config switch so trim / AOT analyzers stay quiet — see the `packaging` guide for the full rationale before flipping either knob.
**Verify your edits with `mur check`** before declaring done. From the project directory: `mur check` (no arguments) runs `dotnet build` and emits one compressed line per diagnostic with a `→ try:` suggestion when the engine recognizes the mistake; `mur check --final` is the explicit "I am done iterating" sweep that emits the full diagnostic set including suppressed iteration-mode warnings. For anything more involved than the build/fix loop — strict-mode failures, custom diagnostic gating, MSBuild passthrough flags — load the `reactor-build-and-check` skill.
## Required imports
```csharp
using System;
using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Reactor.Layout; // FlexDirection, FlexJustify, FlexAlign
using Microsoft.UI.Xaml; // Thickness, HorizontalAlignment, VerticalAlignment
using Microsoft.UI.Xaml.Controls; // Orientation, InfoBarSeverity, etc.
using static Microsoft.UI.Reactor.Factories;
```
## Components
```csharp
// Class component (primary)
class Counter : Component
{
public override Element Render()
{
var (count, setCount) = UseState(0);
return VStack(TextBlock($"Count: {count}"),
Button("+1", () => setCount(count + 1)));
}
}
// Function component (inline, small reusable pieces)
var toggle = Memo(ctx =>
{
var (on, setOn) = ctx.UseState(false);
return ToggleSwitch(on, setOn);
});
// Typed props — use records for free structural equality
record UserCardProps(string Name, string Role);
class UserCard : Component<UserCardProps>
{
public override Element Render() =>
VStack(TextBlock(Props.Name), Caption(Props.Role));
}
Component<UserCard, UserCardProps>(new UserCardProps("Alice", "Admin"))
// Memoized function component
Memo(ctx => TextBlock("Stable")) // render once + own state
Memo(ctx => TextBlock($"Hi, {name}"), name) // re-render when deps change
```
`Component` skips parent-triggered re-renders by default. `Component<TProps>` skips when `Equals(oldProps, newProps)`.
**App entry point.** `ReactorApp.Run<MyRoot>("Title", width: W, height: H)` against a component class (the scaffolded form). `width`/`height` are optional DIPs — omit them to let the OS choose the initial window size. For a tiny demo without a class, the inline form: `ReactorApp.Run("Title", ctx => { var (m, setM) = ctx.UseState("hi"); return TextBlock(m); })`.
## Hooks
**Rules:** same order every render (no hooks in `if`/`for`), only from `Render()` or function-component body.
| Hook | Returns | Use for |
|---|---|---|
| `UseState<T>(initial)` | `(T, Action<T>)` | Primary state |
| `UseReducer<T>(initial)` | `(T, Action<Func<T,T>>)` | State derived from previous (lists) |
| `UseReducer<TState,TAction>(reduce, initial)` | `(TState, Action<TAction>)` | Action-style reducer |
| `UseEffect(action, deps)` | — | Side effects + cleanup |
| `UseMemo<T>(factory, deps)` | `T` | Memoized computation |
| `UseCallback(action, deps)` | `Action` | Stable callback reference |
| `UseRef<T>(initial)` | `Ref<T>` | Mutable ref across renders (access via `.Current`) |
| `UseObservable<T>(source)` | `T` | Track `INotifyPropertyChanged` |
| `UseExternalStore<TSnapshot>(subscribe, getSnapshot)` | `TSnapshot` | Subscribe to an external store (stable `subscribe`, cached snapshot) |
| `UseCollection<T>(coll)` | `IReadOnlyList<T>` | Track `ObservableCollection` |
| `UseContext<T>(ctx)` | `T` | Read tree-scoped ambient state |
| `UsePersisted<T>(key, initial)` | `(T, Action<T>)` | State that survives unmount |
| `UseResource<T>` / `UseInfiniteResource` / `UseMutation` | (see `reactor-async`) | Async data |
| `UseValidationContext()` | `ValidationContext` | (see `reactor-forms`) |
| `UseNavigation<TRoute>(initial)` | `NavigationHandle<TRoute>` | (see `reactor-navigation`) |
### UseState
<!-- index:use-state -->
`UseState<T>(initial)` returns `(value, setValue)`. Calling the setter with a
value that differs from the current one schedules a re-render; the new value is
visible on the *next* render, not on the line after the call.
```csharp
var (count, setCount) = UseState(0);
return VStack(8,
TextBlock($"Count: {count}"),
Button("Increment", () => setCount(count + 1)));
```
Two rules decide whether `UseState` is the right hook:
- **Hook order is constant.** Never call a hook inside `if`, `for`, or a
nested lambda — call them all unconditionally and use the result
conditionally. `REACTOR_HOOKS_001` enforces this.
- **Reference types need a new instance.** `UseState(new List<T>())` followed
by `list.Add(item)` will not re-render: the reference is unchanged, so the
setter never sees a difference. Reach for `UseReducer` instead.
Pass `threadSafe: true` when the setter is called from a background thread.
<!-- /index:use-state -->
### UseReducer
Ver no GitHub