Helpers
@ovok/native re-exports a handful of utilities used internally by other components. They're publicly exported so apps can reuse the same Formik field wrappers, conditional rendering, and vendor-neutral logger that the SDK uses itself. Bottom-sheet components remain on their own package and are not re-exported by the SDK root.
Source: src/modules/helpers/index.tsx.
Exports at a glance
import {
Conditional,
FormField,
FormSelect,
FormDateField,
slog,
configureOvokTelemetry,
startOvokSpan,
recordOvokMetric,
recordOvokDuration,
addOvokSpanEvent,
emitOvokLog,
withOvokSpan,
withAnimated,
} from "@ovok/native";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
| Export | Module path | Source library |
|---|---|---|
Conditional | ./conditional.tsx | SDK |
FormField | ./form-items/form-field.tsx | SDK (wraps react-native-paper TextInput) |
FormSelect | ./form-items/form-select.tsx | SDK (wraps react-native-element-dropdown) |
FormDateField | ./form-items/form-date-field.tsx | SDK (wraps react-native-modal-datetime-picker) |
slog | ./utils/s-log.ts | SDK logger and optional OpenTelemetry logs |
configureOvokTelemetry and telemetry types | ./utils/telemetry.ts | Vendor-neutral provider configuration |
sanitizeOvokTelemetryAttributes and telemetry helpers | ./utils/telemetry.ts | Sanitized spans, metrics, durations, events, logs, and operation wrappers |
withAnimated | ./utils/with-animated.tsx | SDK (Reanimated HOC bridge) |
<Conditional>
Renders children only when condition is truthy. Cleaner than {cond && <View>...</View>} for multi-line branches and easier to scan in deeply-nested trees.
interface ConditionalProps {
condition: boolean;
children: React.ReactNode;
}
<Conditional condition={isMfaRequired}>
<MfaCodeInput onSubmit={handleMfa} />
</Conditional>
condition === false → returns null. No fallback prop; pair with a sibling for else branches.
<FormField>
A Formik-bound text input. Reads value/error/touched from the Formik context you pass via context and routes change/blur back through the form. Renders react-native-paper's <TextInput mode="outlined"> plus a <HelperText> for errors, with the error message run through react-i18next's t().
interface FormFieldProps extends Omit<TextInputProps, "onChangeText" | "value" | "testID"> {
name: string;
context: React.Context<any>;
containerStyle?: ViewStyle;
inputRef?: React.Ref<{ focus: () => void }>;
testID?: string;
}
import { useFormik } from "formik";
import * as React from "react";
import { FormField } from "@ovok/native";
const FormContext = React.createContext<ReturnType<typeof useFormik> | null>(null);
function SignInForm() {
const form = useFormik({
initialValues: { email: "" },
onSubmit: async (values) => { /* ... */ },
});
return (
<FormContext.Provider value={form}>
<FormField name="email" context={FormContext} label="Email" />
</FormContext.Provider>
);
}
Error messages are looked up as translation keys, so feed Yup/Zod schemas key names like "validation.email.required" rather than raw English strings.
<FormSelect>
Formik-bound dropdown with an animated floating label. Wraps react-native-element-dropdown's <Dropdown>. Generic on the option type T.
interface FormSelectProps<T extends object> extends Omit<DropdownProps<T>, "data" | "onChange" | "onBlur" | "placeholder"> {
name: string;
context: React.Context<any>;
label?: string;
options: T[];
labelStyle?: TextStyle;
onChange?: (item: T) => void;
onBlur?: () => void;
}
<FormSelect
name="country"
context={FormContext}
label="Country"
options={countries}
labelField="name"
valueField="code"
/>
The selected value is written via setFieldValue(name, item[valueField]) — so valueField must be a key of T whose value is the discriminator your schema validates.
<FormDateField>
Formik-bound date picker. Wraps react-native-modal-datetime-picker and writes the result back as a YYYY-MM-DD string (via date.toISOString().split('T')[0]). Supports mode: 'date' | 'time' | 'datetime'; default is 'date'.
interface FormDateFieldProps {
name: string;
context: React.Context<any>;
label?: string;
testID?: string;
containerStyle?: ViewStyle;
labelStyle?: TextStyle;
mode?: "date" | "time" | "datetime";
}
<FormDateField name="dateOfBirth" context={FormContext} label="Date of birth" />
Note: every mode is serialized as YYYY-MM-DD; the time component is discarded even
when mode is 'time' or 'datetime'. If you need a time or full ISO timestamp,
write a separate field wrapper. labelStyle is part of the public type but is not
currently applied by the component.
Logging and telemetry
slog(error, scope, additionalData?, level?) is the legacy structured-logging helper.
The current logger writes to the console by default and emits sanitized OpenTelemetry
logs/metrics when the optional OpenTelemetry APIs or explicitly configured providers
are available. The public helper barrel exports configureOvokLogger and
ovokLogger; the sentryLogger name remains an internal deprecated alias.
Structured logger. It accepts an error, free-form scope, optional data, and a log level. Sensitive values are sanitized before they reach the configured logger or telemetry provider.
The sanitizer keeps only the documented operational keys (deviceId, model,
measurementTypeKey, platform, status/error codes, retry fields, and similar
primitive values). The error, message, stack, and nested additionalData passed
to slog are not forwarded as exception details. Use the explicit telemetry span
API when the host needs structured, non-sensitive error context.
slog(
error: any,
scope: string,
additionalData?: Record<string, any>,
level?: "error" | "warning" | "info" | "debug",
): void;
try {
await someBleOperation();
} catch (e) {
slog(e, "bt.connect", { deviceSn: device.sn }, "warning");
}
Use the scope argument to group logs — it is free text, so consistent prefixes such as
bt.*, auth.*, and fhir.* are useful. Configure vendor-neutral providers with
configureOvokTelemetry from @ovok/native; the app owns provider and exporter setup.
The telemetry helpers are intentionally no-ops when no provider is available:
configureOvokTelemetry(options?: OvokTelemetryOptions): void;
sanitizeOvokTelemetryAttributes(attributes?: Record<string, unknown>): OvokTelemetryAttributes;
startOvokSpan(name: string, attributes?: Record<string, unknown>): OvokSpan | undefined;
recordOvokMetric(name: string, value: number, attributes?: Record<string, unknown>): void;
recordOvokDuration(name: string, durationMs: number, attributes?: Record<string, unknown>): void;
addOvokSpanEvent(name: string, attributes?: Record<string, unknown>): void;
emitOvokLog(severityText: string, body: string, attributes?: Record<string, unknown>): void;
withOvokSpan<T>(name: string, attributes: Record<string, unknown>, operation: () => T | Promise<T>): Promise<T>;
Provider calls are not caught by the SDK. A custom tracer, meter, or logger that throws can therefore interrupt the SDK operation that emitted telemetry; providers should be best-effort and non-throwing.
For a custom logger, provide a callback with the public logger types:
import {
configureOvokLogger,
ovokLogger,
type OvokLogLevel,
type OvokLogger,
} from "@ovok/native";
const logger: OvokLogger = (level: OvokLogLevel, message, extra) => {
analytics.log("ovok", { level, message, extra });
};
configureOvokLogger(logger);
ovokLogger("info", "Bluetooth flow started", { provider: "screen" });
Calling configureOvokLogger() without an argument restores the default console
logger. Error and fatal entries also increment the SDK error metric when telemetry is
available.
withAnimated(Component)
Higher-order component that bridges a React Native Paper class component into a react-native-reanimated animated component. Worked around callstack/react-native-paper#2364 — createAnimatedComponent on a class component requires a class wrapper, which is what this HOC supplies.
const withAnimated: <T extends object>(
WrappedComponent: React.ComponentType<T>,
) => React.ComponentType<AnimatedProps<T>>;
import { Card } from "react-native-paper";
import { withAnimated } from "@ovok/native";
const AnimatedCard = withAnimated(Card);
Use only when a Paper class component needs useAnimatedStyle/useSharedValue. Function components don't need this — Animated.createAnimatedComponent accepts them directly.
BottomSheetModalProvider
Mount BottomSheetModalProvider from @gorhom/bottom-sheet once at the root when using <PickerSheet> or another bottom-sheet component. It is intentionally not exported from @ovok/native:
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { ThemeProvider as OvokThemeProvider } from "@ovok/native";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
<OvokThemeProvider theme={themeConfig}>
<YourScreens />
</OvokThemeProvider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
);
}
This is the same provider you'd import from @gorhom/bottom-sheet directly. It is
not a public @ovok/native export; install the peer and import it from
@gorhom/bottom-sheet when your app uses bottom sheets.
Related
- PickerSheet — uses
BottomSheetModalProvider - Sign-In — uses
FormFieldinternally