Name the states you render
A string source selects one explicitly named case. A missing case uses default; without a default it renders nothing. This makes partial case tables possible, but a visible fallback is useful when unexpected server data must not look like an ordinary empty result.
import {createElement as h} from "@fiberfx/hyperscript";
import {event, store} from "@fiberfx/hypergraph";
import {variant} from "@fiberfx/reflect";
const stateChanged = event<string>();
const $state = store("ready").on(stateChanged, (_, state) => state);
const Ready = () => h("p", null, "3 results available");
const Empty = () => h("p", null, "No results; change the filter");
const Failed = () => h("p", null, "Request failed; your draft is retained");
const Unknown = () => h("p", null, "Unknown state");
export const Result = variant({
source: $state,
cases: {ready: Ready, empty: Empty, error: Failed},
default: Unknown,
});
const $visible = store(true);
export const OptionalResult = variant({if: $visible, then: Result});
The boolean form uses if, then and optional else. OptionalResult renders nothing while false because no else is supplied. Both forms accept the same bind, mapProps and hooks concepts as reflect. The selected views should agree on their shared prop contract.
The messages in these tiny views describe UI states; variant does not fetch data, retain a draft or implement retry. Those behaviors belong to your model. Use truthful text only when your model supplies the corresponding behavior.
Select each result state
Open the variant experiment. Select Ready, Empty and Error. Before selecting Unknown: constructor, predict which view should appear.
Compare the empty result with the unknown fallback. They should communicate different situations: a valid request with no results versus a value the interface does not recognize.
Follow explicit case lookup
Ready, Empty and Error should select their named views. constructor should select the explicit fallback. Reflect uses an own-property check, so an inherited object member cannot become a case.
A case change may replace the chosen child. The lifecycle hooks passed to variant belong to its reflected wrapper, however; they do not run on every case change. Put case-specific lifecycle work inside the case component if that work belongs to its own lifetime. The lifecycle chapter explains what hook props capture.
Do not turn unknown into success
An unknown case is not a thrown validation error: the supported behavior is fallback or no output. Do not teach the fallback as evidence that external data was validated. Validate incoming state identifiers in the model when the application requires rejection.
Do not use cases[state] || Ready as an informal replacement for a guarded registry lookup. Besides conflating unknown with ready, a plain object can expose inherited names. The published variant implements the own-property boundary for its case table.
Return to a known state
Restart the exercise and confirm Ready is selected. Switch directly from Unknown to Empty; the empty state should remain a normal, explicit choice. No additional recovery mechanism is needed just to change the source store.
For a real request, define what retry changes in the model before adding a retry button. A screen transition alone is not proof that a request was retried.
Describe a search result honestly
Give a search model explicit states such as loading, ready, empty and error. Map each to a small view, share common props through bindings, and add a fallback for unsupported external states. Keep a previous result visible during loading only if that is an intentional product decision.
Use boolean variant for a simple presence condition. Use the string form when the distinct states deserve distinct explanations. Next, replace one component within an existing view without inventing a slot API.