Automatic capture
How taps, rage taps and crashes are recorded and labeled.
Once RiveoraProvider wraps your app, Riveora records taps, rage taps and crashes with no extra code. This page explains how each one works, how taps get their human-readable labels, and how to keep parts of your app out of tracking.
What is captured
| Event | When | Shown in the dashboard as |
|---|---|---|
element_tapped | Every tap on anything in your app | Taps on each screen, the tap map, each user’s journey |
rage_tap | 3 or more taps within 1 second, each within 40 points of the first | Rage taps, “Worth fixing” on screen pages |
app_error | An unhandled JavaScript error or crash | Crashes, with the screen and the users affected |
screen_visibility_ended | Leaving a screen; includes tap and swipe counts | Time on screen and taps per visit |
request_failed | A request from your app got an error status or no response | The fix list on “Why people leave” |
slow_request | A request took 3 seconds or longer | The fix list on “Why people leave” |
A touch only counts as a tap if the finger moved less than 12 points and lifted within 800 ms. Longer movements are counted as swipes or scrolls, and long presses are ignored.
How taps are labeled
When someone taps, the SDK looks at the element that was touched and walks up to the nearest thing that responds to taps, such as a Pressable, TouchableOpacity, Button or Switch. It then picks a label from the first source that has one:
accessibilityLabel(oraria-label) on the element.- For text fields, the
placeholder. What the user typed is never read. - For buttons and other tappable elements, the visible text inside, e.g. “Add to cart”, when it’s short: at most two pieces of text and 40 characters. Icons (icon-font glyphs) and badges like the “3” in “Inbox 3” are left out.
- The element’s
testID.
Each tap also records:
- Element type: button, text field, switch, text, image or area.
- Component: the nearest component from your own code, e.g.
ProductCard. Available in development and in release builds that keep function names. - Position: where the finger landed, as a share of screen width and height (rounded to 1%), so phones of different sizes line up on the tap map. Not sent for keypad keys.
- Whether it does anything: taps on elements with no tap handler are flagged as “not tappable”. Many of those on one element means people expect it to work.
Icon-only buttons have no text to read. Give them an accessibilityLabel and you get clear labels in Riveora and better support for screen readers at the same time.
<Pressable onPress={save} accessibilityLabel="Save to favourites">
<HeartIcon />
</Pressable>Label safety
- Taps on plain text (a message, an address) never send the text. The tap is still recorded, with its position on the screen.
- Text-heavy rows, such as a contact with a name, a status and a time, or a chat preview, get no text label either: that’s where personal data is shown.
- Repeated items (list rows, grid cells, chips, carousel pages, also a list with a single item) only get a text label when it’s the same on the nearest items built the same way, like “Delete” on every row. A contact’s name, a recent search or a recovery-phrase word is never sent. A shared
testID(testID="contact-row") still names them. On onboarding slides, a button other slides have too (“Next”) keeps its label. - Single characters and lone numbers (“+”, “X”, “12”) are never labels.
- Keypad keys (“4”, “4 GHI”, an accessibility label like “Digit 4”, a
testIDlikepin_4) get no text label and no position, so a PIN can’t be rebuilt from the taps. Their maskedtestID(pin_#) is the only name they get. - Labels are trimmed to 60 characters.
- Any run of 3 or more digits is masked, so “Order 1234567” becomes “Order ######”. With 6 or more digits close together, like a phone number written in small groups, every digit is masked.
- Labels and testIDs that contain an email address or a token (a JWT, “Bearer …”, an API key) are dropped; testIDs get the same digit masking.
- Text inside text fields is never read, at any level.
- Text inside
RiveoraIgnoreis never part of a label, also when the button around it is tapped.
Keeping UI out of tracking
If a screen shows personal information on its buttons, such as an account name, wrap it in RiveoraIgnore. Taps inside are not recorded at all, and its text is never used to label a button around it: a button that wraps an ignored name is labelled by its other text only, or not at all.
import { RiveoraIgnore } from '@riveora/react-native';
<RiveoraIgnore>
<AccountDetails />
</RiveoraIgnore>You can do the same for a single element with testID="riveora-ignore" or nativeID="riveora-ignore". It works however deep the element is, so wrapping a whole screen is fine. Use it for PIN pads, recovery phrases and anything else people pick in private. Screen pictures in development builds still show these areas: it only stops tap tracking.
Naming an element yourself
Menus, plan cards and options built from a list lose their text labels (see above). Wrap them in RiveoraLabel to give their taps a name of your choice. It adds no view, so your layout doesn’t change. Never put personal data in it; a label with an email address or a token isn’t sent.
import { RiveoraLabel } from '@riveora/react-native';
{plans.map((plan) => (
<RiveoraLabel key={plan.id} label={`Plan: ${plan.name}`}>
<PlanCard plan={plan} />
</RiveoraLabel>
))}Options
Pass autocapture to the provider. Use false to switch all automatic capture off, or an object to tune it:
<RiveoraProvider
projectKey="pk_your_project_key"
autocapture={{ taps: true, captureText: false, errors: true, network: true }}
>| Option | Type | Description |
|---|---|---|
taps | boolean | Record taps and rage taps. Default: true |
captureText | boolean | Use a button’s short visible text as its label. When false, only accessibility labels, placeholders and testIDs are used. Default: true |
errors | boolean | Record JavaScript errors and crashes. Default: true |
network | boolean | Record failed and slow HTTP requests. Default: true |
Failed and slow requests
A coupon that won’t apply or a product page that spins for five seconds loses sales, and it never shows up as a crash. Riveora notices requests that fail or take 3 seconds or longer, and shows how many people left right after one. It works with fetch, axios and anything else built on React Native’s networking.
| Recorded | Never recorded |
|---|---|
| Method, host and path, e.g. POST api.shop.com/v1/cart/coupon | Query strings, headers, request or response bodies |
| Status code and how long it took | IDs, emails, phone numbers, tokens, file names and handles in the path: /orders/8812 becomes /orders/:id, and the part after /users/, /u/, /profile/ or /invite/ is hidden |
Riveora only observes: requests, responses and errors pass through untouched. Its own traffic and Metro’s development requests are ignored, and a request that fails over and over in a few seconds (a retry loop) is counted once.
Screen pictures for the tap map
Each screen's page in the dashboard shows taps on top of a picture of that screen. The SDK takes these pictures for you. There's no code to write: install react-native-view-shot with the SDK (the install command in the Quickstart already includes it), rebuild your app once, and open your screens on your test phone.
- Only in development builds. Pictures are taken only when
__DEV__is true, so they come from your own device. Your users' screens are never pictured. - One picture per screen. About 1.5 seconds after a screen opens, the SDK asks Riveora whether it still needs a picture. Once a screen has one, taken automatically or uploaded, no device takes another. To replace it, upload a new one or remove it on the screen's page.
- A checklist of what's missing. Development builds also send your app's list of screens (from Expo Router's route files or React Navigation's navigators), so the Screens page lists every screen, including ones nobody has opened, and shows which still need a picture. Open those in your development build and they tick off within seconds.
- Your uploads win. You can also upload a screenshot on a screen's page. An uploaded picture is never replaced automatically.
- Each person's taps, too. In a user's journey, Show taps on screen puts that person's taps on the same picture, numbered in the order they happened. A pin marks exactly where their finger touched, but the picture is the one from your development build, so their screen may have looked different: scrolled, showing other content, or from another version of your app (the dialog says when the versions differ). The text under the picture always names what they actually tapped.
- Small and private. Pictures are shrunk to about 420 pixels wide and stored as small WebP files. They’re only taken while consent is
granted. A picture shows the whole screen, including areas insideRiveoraIgnore, so use demo data on your test device. Long screenshots you upload are cropped to their top part.
react-native-view-shot is a native module. Expo Go includes it. In a development build, rebuild the app after installing it: npx expo run:ios or npx expo run:android. If you build from Xcode, run npx pod-install first. Then close the app completely and open the new build: reloading in Metro doesn’t load native code. Until you do, the console shows “Screen pictures for the dashboard are off”, pictures are skipped, and everything else works as normal.
To turn pictures off, pass screenshots={false} to RiveoraProvider.
Crashes
The SDK registers a global JavaScript error handler. It records the error, sends it right away if the error is fatal, then passes it on to whatever handler was installed before, so crash reporters like Sentry keep working exactly as they did. Error messages are cut to 200 characters, with emails, long numbers, tokens, the query part of web addresses and files on the device (file://…, content://…) masked. Stack traces are never sent.
Native crashes, which happen outside JavaScript, aren’t captured. Keep your existing crash reporter for those.