> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/beyond-apex/profiling/user-interactions.md).

# User Interactions

A "backend call" is a single trip to Apex. A "user interaction" is everything that happens between the user clicking a button and seeing the result — which may involve several backend calls, some client-side work, and a final UI update.

`timeUserInteraction(interactionName, fn)` is the umbrella timer for those higher-level flows. It uses the same `PerformanceCallBuilder` as [Backend Calls](/beyond-apex/profiling/backend-calls.md), so the error-handling story is identical (default, `withoutRethrow`, custom handler, both). The only difference is the log type — interactions land as `TYPE.USER_INTERACTION` and the error context exposes `interactionName` instead of `methodName`.

## Basic Usage

{% code title="formHandler.js" lineNumbers="true" %}

```javascript
import Triton from 'c/triton';
import submitFormData from '@salesforce/apex/FormController.submitFormData';
import validateFormData from '@salesforce/apex/FormController.validateFormData';

export default class FormHandler extends LightningElement {
    triton;
    formData = {};

    connectedCallback() {
        this.triton = new Triton().bindToComponent('FormHandler');
        this.triton.trackComponentLifecycle('connected');
    }

    async handleSubmit() {
        await this.triton.timeUserInteraction('form-submit', async () => {
            const data = this.collectFormData();

            const validation = await this.triton.timeBackendCall(
                'validateFormData',
                () => validateFormData({ data })
            ).execute();

            if (!validation.isValid) {
                throw new Error(`Form validation failed: ${validation.errors.join(', ')}`);
            }

            const result = await this.triton.timeBackendCall(
                'submitFormData',
                () => submitFormData({ data })
            ).execute();

            this.showSuccessMessage(result);
            return result;
        }).execute();
    }
}
```

{% endcode %}

What you get out of this:

* One `User interaction started: form-submit` log when the user clicks.
* Two `Backend call started/completed` pairs for the validate + submit Apex methods.
* One `User interaction completed: form-submit` log at the end, with the *total* duration including both backend calls and any client-side work in between.

That parent-child relationship — interaction with nested backend calls — is the key reason to use `timeUserInteraction` even when "it's just one Apex call". It gives you a single record per user click that aggregates the whole experience.

## Nested Calls With Per-Step Resilience

Real-world flows often want different error policies per step. Here's a payment flow where notification failures don't kill the transaction:

```javascript
async handleComplexUserFlow() {
    await this.triton.timeUserInteraction('payment-flow', async () => {
        const validation = await this.triton.timeBackendCall(
            'validatePaymentInfo',
            () => validatePaymentInfo({ cardToken: this.cardToken })
        ).execute();

        if (!validation.isValid) {
            throw new Error('Invalid payment information');
        }

        const paymentResult = await this.triton.timeBackendCall(
            'processPayment',
            () => processPayment({ amount: this.amount, cardToken: this.cardToken })
        ).execute();

        // Non-critical: log the failure but don't break the flow.
        await this.triton.timeBackendCall(
            'sendNotification',
            () => sendNotification({
                type: 'payment_success',
                userId: this.userId,
                paymentId: paymentResult.id
            })
        )
        .withCustomErrorHandler((error, context) => {
            console.warn(`Notification failed after ${context.duration}ms:`, error);
        })
        .withoutRethrow()
        .execute();

        return paymentResult;
    })
    .withCustomErrorHandler((error, context) => {
        // context.interactionName === 'payment-flow'
        console.error(`Payment flow failed for ${context.interactionName} after ${context.duration}ms:`, error);
        this.showPaymentFlowError(error);
    })
    .withoutRethrow()
    .execute();
}
```

Validation and processing rethrow (so a failure short-circuits the flow), notifications swallow their own errors, and the outer interaction wrap catches everything that escaped — surfacing a single user-facing error and a single top-level failure log.

## Custom Handler Context

Custom error handlers on user interactions receive:

```javascript
{ interactionName: 'form-submit', duration: 1234 }
```

Contrast with backend calls, which use `methodName` instead of `interactionName`. The duration is measured from `timeUserInteraction(...)` to the moment the error was thrown.

## When to Use Interactions vs. Backend Calls

| If the work is...                                                       | Use                                                                                                 |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| A single Apex round trip                                                | [`timeBackendCall`](/beyond-apex/profiling/backend-calls.md)                                        |
| A user-triggered flow with one or more Apex calls and client-side logic | `timeUserInteraction` (often wrapping `timeBackendCall` inside)                                     |
| Purely client-side timing (DOM work, animations, sync work)             | [`startPerformanceMark` / `endPerformanceMark`](/beyond-apex/profiling/custom-marks.md)             |
| Component mount / unmount / re-render telemetry                         | [`trackComponentLifecycle` / `trackComponentRender`](/beyond-apex/profiling/component-lifecycle.md) |

## Best Practices

* Use a stable, descriptive `interactionName`. Treat it like an event name (`'checkout-submit'`, `'opportunity-clone'`) — it'll become a primary grouping key in dashboards.
* When nesting, let inner failures rethrow by default. The outer interaction will record the failure and you don't need to duplicate logging at every level.
* Don't reach for `timeUserInteraction` when there's no user. For background or lifecycle work, the more specific helpers below give better signal.
