# LWC Flow Integration Guide

This guide covers building Lightning Web Components for use in Salesforce Flow Screens.

---

## Overview

```
┌─────────────────────────────────────────────────────────────────────┐
│                     FLOW ↔ LWC COMMUNICATION                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   ┌─────────────────┐                    ┌─────────────────┐        │
│   │      FLOW       │                    │       LWC       │        │
│   │    Variables    │                    │   Component     │        │
│   └────────┬────────┘                    └────────┬────────┘        │
│            │                                      │                  │
│            │ @api (inputOnly)                     │                  │
│            ├─────────────────────────────────────▶│                  │
│            │                                      │                  │
│            │ FlowAttributeChangeEvent (outputOnly)│                  │
│            │◀─────────────────────────────────────┤                  │
│            │                                      │                  │
│            │ FlowNavigationFinishEvent            │                  │
│            │◀─────────────────────────────────────┤                  │
│            │  (NEXT, BACK, FINISH, PAUSE)         │                  │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Quick Reference

| Direction        | Mechanism                      | Use Case                       |
| ---------------- | ------------------------------ | ------------------------------ |
| Flow → LWC       | `@api` with `role="inputOnly"` | Pass context data to component |
| LWC → Flow       | `FlowAttributeChangeEvent`     | Return user selections/data    |
| LWC → Navigation | `FlowNavigationFinishEvent`    | Trigger Next/Back/Finish       |

---

## Meta.xml Configuration

### Target Configuration

```xml
<targets>
    <target>lightning__FlowScreen</target>
</targets>
```

### Property Roles

```xml
<targetConfig targets="lightning__FlowScreen">
    <!-- INPUT: Flow → Component -->
    <property
        name="recordId"
        type="String"
        label="Record ID"
        description="ID from Flow"
        role="inputOnly"/>

    <!-- OUTPUT: Component → Flow -->
    <property
        name="selectedValue"
        type="String"
        label="Selected Value"
        description="User's selection"
        role="outputOnly"/>
</targetConfig>
```

### Supported Property Types

| Type                   | Description        | Example                  |
| ---------------------- | ------------------ | ------------------------ |
| `String`               | Text values        | Record IDs, names        |
| `Boolean`              | True/false         | Flags, completion status |
| `Integer`              | Whole numbers      | Counts, indexes          |
| `Date`                 | Date values        | Due dates                |
| `DateTime`             | Date and time      | Timestamps               |
| `@salesforce/schema/*` | SObject references | Record types             |

---

## FlowAttributeChangeEvent

This is the **critical** mechanism for sending data back to Flow.

### Import

```javascript
import { FlowAttributeChangeEvent } from 'lightning/flowSupport';
```

### Usage

```javascript
// Dispatch event to update Flow variable
// First param: @api property name (must match meta.xml exactly)
// Second param: new value
this.dispatchEvent(
  new FlowAttributeChangeEvent(
    'selectedRecordId', // Property name
    this.recordId // Value
  )
);
```

### Example: Selection Handler

```javascript
@api selectedRecordId;
@api selectedRecordName;

handleSelect(event) {
    const id = event.target.dataset.id;
    const name = event.target.dataset.name;

    // Update local properties
    this.selectedRecordId = id;
    this.selectedRecordName = name;

    // Notify Flow of BOTH changes
    this.dispatchEvent(new FlowAttributeChangeEvent('selectedRecordId', id));
    this.dispatchEvent(new FlowAttributeChangeEvent('selectedRecordName', name));
}
```

### Common Mistake

```javascript
// ❌ WRONG: Only updating local property
this.selectedRecordId = id;

// ✅ CORRECT: Update AND dispatch event
this.selectedRecordId = id;
this.dispatchEvent(new FlowAttributeChangeEvent('selectedRecordId', id));
```

---

## FlowNavigationFinishEvent

Programmatically trigger Flow navigation from your component.

### Import

```javascript
import { FlowNavigationFinishEvent } from 'lightning/flowSupport';
```

### Navigation Actions

| Action     | Description             | When Available     |
| ---------- | ----------------------- | ------------------ |
| `'NEXT'`   | Go to next screen       | Mid-flow screens   |
| `'BACK'`   | Go to previous screen   | After first screen |
| `'FINISH'` | Complete the flow       | Final screens      |
| `'PAUSE'`  | Pause flow (if enabled) | Pausable flows     |

### Usage

```javascript
// Navigate to next screen
this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));

// Navigate back
this.dispatchEvent(new FlowNavigationFinishEvent('BACK'));

// Finish the flow
this.dispatchEvent(new FlowNavigationFinishEvent('FINISH'));
```

### Check Available Actions

Flow provides available actions via a special `@api` property:

```javascript
// Automatically populated by Flow runtime
@api availableActions = [];

get canGoNext() {
    return this.availableActions.includes('NEXT');
}

get canGoBack() {
    return this.availableActions.includes('BACK');
}

handleNext() {
    if (this.canGoNext) {
        this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));
    }
}
```

### Conditional Navigation Buttons

```html
<template lwc:if="{canGoBack}">
  <lightning-button label="Back" onclick="{handleBack}"></lightning-button>
</template>

<template lwc:if="{canGoNext}">
  <lightning-button label="Next" variant="brand" onclick="{handleNext}"></lightning-button>
</template>
```

---

## Validation Before Navigation

Always validate before allowing navigation:

```javascript
handleNext() {
    // Validate
    if (!this.selectedRecordId) {
        this.errorMessage = 'Please select a record.';
        this.dispatchEvent(new FlowAttributeChangeEvent('errorMessage', this.errorMessage));
        return;
    }

    // Clear error
    this.errorMessage = null;
    this.dispatchEvent(new FlowAttributeChangeEvent('errorMessage', null));

    // Mark complete and navigate
    this.isComplete = true;
    this.dispatchEvent(new FlowAttributeChangeEvent('isComplete', true));
    this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));
}
```

---

## Apex Integration in Flow Context

### Wire Service

```javascript
import { wire } from 'lwc';
import getRecords from '@salesforce/apex/MyController.getRecords';

@api recordId; // From Flow

@wire(getRecords, { parentId: '$recordId' })
wiredRecords({ error, data }) {
    if (data) {
        this.records = data;
    } else if (error) {
        this.error = this.reduceErrors(error);
    }
}
```

### Imperative Calls

```javascript
import processRecord from '@salesforce/apex/MyController.processRecord';

async handleProcess() {
    this.isLoading = true;
    try {
        const result = await processRecord({
            recordId: this.selectedRecordId
        });

        if (result.success) {
            this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));
        } else {
            this.errorMessage = result.message;
            this.dispatchEvent(new FlowAttributeChangeEvent('errorMessage', result.message));
        }
    } catch (error) {
        this.errorMessage = this.reduceErrors(error);
    } finally {
        this.isLoading = false;
    }
}
```

---

## Flow Context Variables

Flow provides special context via reserved variable names:

```xml
<!-- In Flow Builder, map these to your component -->
<property name="recordId" value="{!$Record.Id}"/>
<property name="objectApiName" value="{!$Record.Object}"/>
```

---

## Testing LWC in Flows

### Jest Testing

```javascript
import { createElement } from 'lwc';
import FlowScreenComponent from 'c/flowScreenComponent';
import { FlowAttributeChangeEvent, FlowNavigationFinishEvent } from 'lightning/flowSupport';

// Mock the flow support module
jest.mock(
  'lightning/flowSupport',
  () => ({
    FlowAttributeChangeEvent: jest.fn(),
    FlowNavigationFinishEvent: jest.fn(),
  }),
  { virtual: true }
);

describe('c-flow-screen-component', () => {
  afterEach(() => {
    while (document.body.firstChild) {
      document.body.removeChild(document.body.firstChild);
    }
    jest.clearAllMocks();
  });

  it('dispatches FlowAttributeChangeEvent on selection', async () => {
    const element = createElement('c-flow-screen-component', {
      is: FlowScreenComponent,
    });
    element.availableActions = ['NEXT', 'BACK'];
    document.body.appendChild(element);

    // Simulate selection
    const tile = element.shadowRoot.querySelector('.record-tile');
    tile.click();

    // Verify event dispatched
    expect(FlowAttributeChangeEvent).toHaveBeenCalled();
  });

  it('dispatches FlowNavigationFinishEvent on next', async () => {
    const element = createElement('c-flow-screen-component', {
      is: FlowScreenComponent,
    });
    element.availableActions = ['NEXT'];
    element.selectedRecordId = '001xx000000001';
    document.body.appendChild(element);

    // Click next button
    const nextButton = element.shadowRoot.querySelector('lightning-button[label="Next"]');
    nextButton.click();

    // Verify navigation event
    expect(FlowNavigationFinishEvent).toHaveBeenCalledWith('NEXT');
  });
});
```

### Manual Testing

1. Create a Screen Flow in Setup
2. Add your LWC component to a screen
3. Map input/output variables
4. Test in Flow debug mode
5. Verify variable values in debug panel

---

## Common Patterns

### Selection with Confirmation

```javascript
handleSelect(event) {
    this.selectedId = event.target.dataset.id;
    // Don't navigate yet - wait for explicit confirmation
}

handleConfirm() {
    if (!this.selectedId) {
        this.showError('Please select an item');
        return;
    }

    this.dispatchEvent(new FlowAttributeChangeEvent('selectedId', this.selectedId));
    this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));
}
```

### Multi-Select to Collection

```javascript
@api selectedIds = [];

handleToggle(event) {
    const id = event.target.dataset.id;

    if (this.selectedIds.includes(id)) {
        this.selectedIds = this.selectedIds.filter(i => i !== id);
    } else {
        this.selectedIds = [...this.selectedIds, id];
    }

    // Send collection back to Flow
    this.dispatchEvent(new FlowAttributeChangeEvent('selectedIds', this.selectedIds));
}
```

### Conditional Screen (Skip Logic)

```javascript
connectedCallback() {
    // Auto-skip if condition met
    if (this.shouldSkip) {
        this.dispatchEvent(new FlowNavigationFinishEvent('NEXT'));
    }
}
```

---

## Troubleshooting

| Issue                          | Cause                            | Solution                                             |
| ------------------------------ | -------------------------------- | ---------------------------------------------------- |
| Output not updating in Flow    | Missing FlowAttributeChangeEvent | Always dispatch event after updating @api property   |
| Navigation buttons not showing | Wrong availableActions           | Check Flow provides availableActions correctly       |
| Component not appearing        | Missing `isExposed: true`        | Set in meta.xml                                      |
| Properties not mapping         | Role mismatch                    | Use `inputOnly` for inputs, `outputOnly` for outputs |
| Values reset on navigation     | Local state not persisted        | Use @api properties for all persisted data           |

---

## Template

Use the template at `templates/flow-screen-component/` as a starting point.

---

## Passing sObjects and Wrapper Classes to Flow

### Overview

Flow can receive complex Apex types through `apex://` type bindings. This enables:

- Passing sObjects directly (not just IDs)
- Passing wrapper/DTO classes with multiple fields
- Two-way data binding for record editing

### apex:// Type Syntax

In your meta.xml, reference Apex classes using the `apex://` prefix:

```xml
<targetConfig targets="lightning__FlowScreen">
    <!-- Pass entire Account record -->
    <property
        name="accountRecord"
        type="apex://Account"
        label="Account Record"
        role="inputOnly"/>

    <!-- Pass custom wrapper class -->
    <property
        name="orderSummary"
        type="apex://OrderController.OrderSummaryWrapper"
        label="Order Summary"
        role="inputOnly"/>

    <!-- Output a modified record -->
    <property
        name="updatedAccount"
        type="apex://Account"
        label="Updated Account"
        role="outputOnly"/>
</targetConfig>
```

### Wrapper Class Requirements

Apex wrapper classes must be **public** and have **public properties**:

```apex
public class OrderController {

    // Wrapper class for Flow
    public class OrderSummaryWrapper {
        @AuraEnabled public String orderId;
        @AuraEnabled public String orderName;
        @AuraEnabled public Decimal totalAmount;
        @AuraEnabled public List<LineItemWrapper> lineItems;
        @AuraEnabled public Account customer;
    }

    public class LineItemWrapper {
        @AuraEnabled public String productName;
        @AuraEnabled public Integer quantity;
        @AuraEnabled public Decimal unitPrice;
    }

    // Invocable method to create the wrapper
    @InvocableMethod(label='Get Order Summary')
    public static List<OrderSummaryWrapper> getOrderSummary(List<Id> orderIds) {
        // Query and build wrapper...
    }
}
```

### Using sObjects in LWC

```javascript
import { api, LightningElement } from 'lwc';
import { FlowAttributeChangeEvent } from 'lightning/flowSupport';

export default class AccountEditor extends LightningElement {
  // Receive sObject from Flow
  @api accountRecord;

  // Track local modifications
  _modifiedAccount;

  connectedCallback() {
    // Create a working copy
    this._modifiedAccount = { ...this.accountRecord };
  }

  handleNameChange(event) {
    this._modifiedAccount.Name = event.target.value;
  }

  handleSave() {
    // Send modified record back to Flow
    this.dispatchEvent(new FlowAttributeChangeEvent('updatedAccount', this._modifiedAccount));
  }
}
```

### Using Wrapper Classes in LWC

```javascript
import { api, LightningElement } from 'lwc';

export default class OrderSummaryViewer extends LightningElement {
  @api orderSummary; // apex://OrderController.OrderSummaryWrapper

  get formattedTotal() {
    return this.orderSummary?.totalAmount?.toLocaleString('en-US', {
      style: 'currency',
      currency: 'USD',
    });
  }

  get lineItems() {
    return this.orderSummary?.lineItems || [];
  }

  get customerName() {
    // Access nested sObject
    return this.orderSummary?.customer?.Name || 'Unknown';
  }
}
```

### Flow Configuration for apex:// Types

1. **Create an Invocable Action** that returns your wrapper:

   ```apex
   @InvocableMethod
   public static List<MyWrapper> getData(List<String> inputs) { ... }
   ```

2. **In Flow Builder**, call the Invocable Action before the screen

3. **Store result** in an Apex-Defined Variable

4. **Pass to LWC** via the screen component input mapping

### Common Patterns

#### Pattern 1: Record Edit with Validation

```javascript
// LWC that receives, edits, and returns an sObject
@api inputRecord;      // apex://Contact (inputOnly)
@api outputRecord;     // apex://Contact (outputOnly)
@api isValid = false;  // Boolean (outputOnly)

handleFieldChange(event) {
    const field = event.target.dataset.field;
    this.workingRecord[field] = event.target.value;

    // Validate and update outputs
    this.isValid = this.validateRecord();
    this.dispatchEvent(new FlowAttributeChangeEvent('outputRecord', this.workingRecord));
    this.dispatchEvent(new FlowAttributeChangeEvent('isValid', this.isValid));
}
```

#### Pattern 2: Multi-Record Selection

```javascript
// Select from a list, output selected items
@api availableRecords;  // apex://Account[] (inputOnly)
@api selectedRecords = []; // apex://Account[] (outputOnly)

handleSelect(event) {
    const id = event.target.dataset.id;
    const record = this.availableRecords.find(r => r.Id === id);

    if (record && !this.selectedRecords.find(r => r.Id === id)) {
        this.selectedRecords = [...this.selectedRecords, record];
        this.dispatchEvent(
            new FlowAttributeChangeEvent('selectedRecords', this.selectedRecords)
        );
    }
}
```

#### Pattern 3: Master-Detail Editing

```javascript
// Edit parent with nested child records
@api orderWrapper;  // apex://OrderController.OrderWithLines (inputOnly)
@api updatedOrder;  // apex://OrderController.OrderWithLines (outputOnly)

handleLineItemChange(event) {
    const index = event.target.dataset.index;
    const field = event.target.dataset.field;
    const value = event.target.value;

    // Update nested structure
    const updated = JSON.parse(JSON.stringify(this.orderWrapper));
    updated.lineItems[index][field] = value;

    // Recalculate totals
    updated.totalAmount = updated.lineItems.reduce(
        (sum, item) => sum + (item.quantity * item.unitPrice), 0
    );

    this.updatedOrder = updated;
    this.dispatchEvent(new FlowAttributeChangeEvent('updatedOrder', updated));
}
```

### Limitations

| Limitation                       | Workaround                                           |
| -------------------------------- | ---------------------------------------------------- |
| No `@JsonAccess` support         | Ensure wrapper classes don't require JSON annotation |
| 1000 record limit per collection | Paginate or filter in Apex before passing            |
| No generic types                 | Create specific wrapper classes                      |
| Complex nesting depth            | Flatten deep hierarchies                             |

### Debugging Tips

1. **Console log received data** to verify structure:

   ```javascript
   connectedCallback() {
       console.log('Received from Flow:', JSON.stringify(this.inputWrapper));
   }
   ```

2. **Check Apex class visibility** - inner classes need `public` modifier

3. **Verify @AuraEnabled** on all properties you need to access

---

## Cross-Skill Integration

| Integration         | See Also                                                               |
| ------------------- | ---------------------------------------------------------------------- |
| Flow → Apex → LWC   | [triangle-pattern.md](triangle-pattern.md)                             |
| Apex @AuraEnabled   | [sf-apex/docs/best-practices.md](../../sf-apex/docs/best-practices.md) |
| Flow Templates      | [sf-flow/templates/](../../sf-flow/templates/)                         |
| Async Notifications | [async-notification-patterns.md](async-notification-patterns.md)       |
| State Management    | [state-management.md](state-management.md)                             |
