useAdaptiveCard
Headless state, validation, visibility and action dispatch for the native Adaptive Cards renderer. Drives the AdaptiveCard component and is exported so apps can wire custom toolbars or server-side validation.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-adaptive-cardpnpm add reactThe hook ships with the AdaptiveCard component (it imports the card's types and lib/ helpers). It is also re-exported from @/components/docyrus-native/adaptive-card, and the old components/adaptive-card/use-adaptive-card path still resolves.
Overview
useAdaptiveCard owns the runtime state of one card. The public API matches web @docyrus/ui 1:1.
- Inputs. Every visible
Input.*element is registered byid, with its live value and validation state. - Visibility overrides.
Action.ToggleVisibilityresults are layered on top of the staticisVisibledefaults. - Show-card state. Each
Action.ShowCardbutton has an open or closed flag. - Action dispatch.
submit,execute,openUrl,toggleVisibility,showCardandresetInputsare emitted as typed events. - Submit payload assembly. The hook walks the visible tree, honours
associatedInputs: 'auto' | 'none'and mergesaction.data. - Focus on the first invalid input. When a submit or execute fails validation, the hook focuses the first invalid input. The native equivalent of the web
[aria-invalid]DOM lookup is a focus registry:Input.TextandInput.Numberregister theirTextInputref, so the keyboard opens on that field and the enclosing scroll view brings it into view.
<AdaptiveCard /> uses this hook internally. Call it yourself only when you need one of these:
- Trigger actions programmatically, for example from a native header button.
- Read input state from outside the card.
- Wire server-side validation that returns errors per input id.
Usage
Custom integration
import { Button, View } from 'react-native';
import {
AdaptiveCardView,
useAdaptiveCard,
type AdaptiveCardActionEvent,
type AdaptiveCardPayload
} from '@/components/docyrus-native/adaptive-card';
export function CardWithToolbar({ payload }: { payload: AdaptiveCardPayload }) {
function handleAction(event: AdaptiveCardActionEvent) {
console.log(event);
}
const adaptive = useAdaptiveCard(payload, { onAction: handleAction });
return (
<View className="gap-3">
<Button title="Validate" onPress={() => adaptive.controls.validateAll()} />
<AdaptiveCardView cardProps={adaptive.cardProps} />
</View>
);
}Reading the current input state
const adaptive = useAdaptiveCard(payload);
console.log(adaptive.state.inputs.email);
console.log(adaptive.state.validations.email);Submitting programmatically
const submitAction = payload.actions?.find(a => a.type === 'Action.Submit');
if (submitAction && submitAction.type === 'Action.Submit') {
adaptive.controls.submit(submitAction);
}Resetting after a successful save
async function handleAction(event: AdaptiveCardActionEvent) {
if (event.type !== 'submit') return;
await api.post('/forms', event.data);
adaptive.controls.reset(); // clears inputs, visibility overrides and show-card state
}API Reference
function useAdaptiveCard(card: AdaptiveCardPayload, options?: UseAdaptiveCardOptions): UseAdaptiveCardReturn;UseAdaptiveCardOptions
| Option | Type | Default | Description |
|---|---|---|---|
onAction | (event: AdaptiveCardActionEvent) => void | Promise<void> | — | Receives every dispatched action. |
hostConfig | AdaptiveCardHostConfigOverride | — | Deep-merged onto defaultHostConfig. |
customElements | Record<string, ElementRenderer> | {} | Renderers for unknown type strings. |
onChoiceQuery | (request: AdaptiveCardChoiceQueryRequest) => Promise<Array<AdaptiveCardChoice>> | — | Async resolver for a filtered Input.ChoiceSet whose choices.data is a Data.Query. The input is debounced, and the returned choices are merged with the static choices. A throwing resolver yields []. |
AdaptiveCardChoiceQueryRequest
| Field | Type | Description |
|---|---|---|
dataset | string | The Data.Query dataset name. |
search | string | The current filter text. |
inputId | string | The Input.ChoiceSet id. |
UseAdaptiveCardReturn
| Field | Type | Description |
|---|---|---|
card | AdaptiveCardPayload | The payload passed in. |
hostConfig | AdaptiveCardHostConfig | The resolved host config: the default deep-merged with your override. |
state.inputs | Record<string, AdaptiveCardInputValue> | Live input values, keyed by id. |
state.validations | Record<string, ValidationResult> | Validation for each input ({ isValid, errorMessage? }), recomputed on every change. |
state.visibilityOverrides | Record<string, boolean> | Action.ToggleVisibility overrides, keyed by element id. |
state.showCardOpen | Record<string, boolean> | Open flags for Action.ShowCard, keyed by the action's id or title. |
state.hasSubmittedOnce | boolean | Becomes true after the first failed submit, which is when per-input errors start to show. |
controls.setInput(id, value) | (id, value) => void | Sets an input imperatively. |
controls.submit(action) | (action: AdaptiveCardActionSubmit) => void | Validates, then dispatches 'submit'. On failure, focuses the first invalid input instead. |
controls.execute(action) | (action: AdaptiveCardActionExecute) => void | Validates, then dispatches 'execute' with verb. On failure, focuses the first invalid input instead. |
controls.openUrl(action) | (action: AdaptiveCardActionOpenUrl) => void | Dispatches 'openUrl'. |
controls.toggleVisibility(action) | (action: AdaptiveCardActionToggleVisibility) => void | Applies the visibility overrides, then dispatches 'toggleVisibility'. |
controls.toggleShowCard(action) | (action: AdaptiveCardActionShowCard) => void | Flips a show-card, then dispatches 'showCard'. |
controls.resetInputs(action) | (action: AdaptiveCardActionResetInputs) => void | Clears the target inputs (all of them when targetInputIds is empty), then dispatches 'resetInputs'. |
controls.validateAll() | () => { ok; firstInvalidId?; errors } | Runs validation immediately. It does not dispatch or change focus. |
controls.reset() | () => void | Clears inputs, visibility overrides and show-card state. |
cardProps | AdaptiveCardContextValue | The value <AdaptiveCardView /> consumes. On native it also carries registerInputFocus for the focus registry. |
When validation runs
- The first submit validates silently. If any input fails, the hook sets
state.hasSubmittedOnce = true, focuses the first invalid focusable input, and does not dispatch. - After that failed submit, validation re-renders live, so each error message clears once the user fixes the field.
- Date, time, toggle, choice-set and rating inputs have no text caret to focus. They show their error state but are not focused.
The associatedInputs contract
Action.Submit and Action.Execute build the submit payload from the currently visible card tree:
'auto'(default): collects everyInput.*that is visible under the currentvisibilityOverrides. Hidden inputs are left out.'none': skips inputs and emits only the literalaction.data.
Type Exports
| Type | Description |
|---|---|
UseAdaptiveCardOptions | The hook options. |
UseAdaptiveCardReturn | The full hook return value. |
AdaptiveCardChoiceQueryRequest | The request passed to onChoiceQuery. |
AdaptiveCardInputValue | string | Array<string> | boolean | number | null. |
Overview
Reusable React Native hooks for Docyrus-connected mobile applications.
useDataExport
Client-side export of already-loaded rows to CSV, JSON, Markdown or Excel on React Native. Projects rows through a column list, writes the file into the cache directory and opens the share sheet. API-aligned with the web useDataExport.