Handling polling collectors in React Native
PingOne React Native
A DaVinci flow can use polling when the user must complete an action outside the current screen. For example, a user might approve a request on another device, scan a QR code, or complete an out-of-band verification step. The app can show progress while it waits for the server to report the result.
The React Native bridge exposes this state as a POLLING collector and provides the pollStatus() action for monitoring it.
Polling is output-only: it does not provide a value for ordinary form submission.
Monitoring polling status
The pollStatus() action reports an intermediate continue status and one terminal status: complete, timedOut, expired, or error.
The callback receives the exact status object from the bridge.
The following example keeps the latest status in React state, advances the flow when a terminal status arrives, and removes the local listener when the component unmounts:
import { useEffect, useState } from 'react';
import { Text } from 'react-native';
import { useDaVinci } from '@ping-identity/rn-davinci';
import type { PollingStatus } from '@ping-identity/rn-davinci';
function PollingField({ collectorKey }) {
const { node, pollStatus, next } = useDaVinci(davinciClient);
const [status, setStatus] = useState<PollingStatus | null>(null);
useEffect(() => {
if (node?.type !== 'ContinueNode') return;
let unsubscribe: (() => void) | undefined;
let active = true;
setStatus(null);
void pollStatus(
nextStatus => {
if (active) setStatus(nextStatus);
if (nextStatus.status !== 'continue') {
next({ collectors: [] }).catch(() => {
// next() records the failure in the hook's `error` state.
});
}
},
{ key: collectorKey },
)
.then(removeListener => {
if (active) {
unsubscribe = removeListener;
} else {
removeListener();
}
})
.catch(() => {
// pollStatus() rejects when the node has no polling collector.
// This rejection is not recorded in the hook's `error` state.
});
return () => {
active = false;
unsubscribe?.();
};
}, [collectorKey, node, pollStatus, next]);
if (!status || status.status === 'continue') {
const progress =
status?.status === 'continue'
? ` (${status.retryCount}/${status.maxRetries})`
: '';
return <Text>Waiting for approval{progress}…</Text>;
}
if (status.status === 'complete') {
return <Text>Approval complete.</Text>;
}
if (status.status === 'timedOut') {
return <Text>The approval timed out.</Text>;
}
if (status.status === 'expired') {
return <Text>The approval expired.</Text>;
}
return <Text>Polling failed: {status.error.message}</Text>;
}
When multiple polling collectors exist, pass the required collector key in the key option.
If you omit the option, the SDK monitors the first polling collector on the active node.
When the node has no polling collector, pollStatus() rejects.
Handle the rejection in your app, for example by logging it or displaying a message.
The hook does not record this rejection in its error state, because pollStatus() returns its result as a Promise rather than through shared hook state.
Continuing after polling
pollStatus() does not call next() automatically.
After a terminal status, decide how your app should continue and submit the active ContinueNode explicitly, for example with next({ collectors: [] }) when the flow expects no additional values.
The example in the previous section calls next({ collectors: [] }) as soon as the terminal status arrives.
If your flow requires additional values after polling, submit them instead.
The terminal status ends event delivery for that polling subscription. The returned unsubscribe function stops local JavaScript delivery only; it does not cancel the native poll. Native polling continues until it completes or reaches its configured timeout and retry limit.
If the app leaves the screen, call the unsubscribe function to stop updating that screen. Do not treat that function as a native cancellation API.
See Stepping through DaVinci flows for the common node and submission model.