Skip to main content

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";
ExportModule pathSource library
Conditional./conditional.tsxSDK
FormField./form-items/form-field.tsxSDK (wraps react-native-paper TextInput)
FormSelect./form-items/form-select.tsxSDK (wraps react-native-element-dropdown)
FormDateField./form-items/form-date-field.tsxSDK (wraps react-native-modal-datetime-picker)
slog./utils/s-log.tsSDK logger and optional OpenTelemetry logs
configureOvokTelemetry and telemetry types./utils/telemetry.tsVendor-neutral provider configuration
sanitizeOvokTelemetryAttributes and telemetry helpers./utils/telemetry.tsSanitized spans, metrics, durations, events, logs, and operation wrappers
withAnimated./utils/with-animated.tsxSDK (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.