LWCのレコードアクションで画面を順に切り替える実装
1つの画面に項目を詰め込むと、入力の負担が増えます。ステップに分けて、入力済みの値を保ったまま前後できるレコードアクションの作り方を書きます。
なぜ画面を分けるのか
取引先責任者を作るのに、日付・数値・テキストをまとめて1画面に並べると、必須項目の見落としが増えます。ステップに分けて1画面1テーマにすると、入力の負担が下がります。
ここで作るのは、取引先のレコードページに置くクイックアクションです。ボタンを押すと、日付選択・数値入力・テキスト入力の3画面が順に開き、前の画面へ戻っても入力した値は消えません。
ページそのものを移動するNavigationMixinとは違います。ここでの切り替えは、1つのコンポーネントの中でテンプレートの表示・非表示を切り替えるだけで、URLは変わりません。同じ複数画面の入力を画面フロー(Screen Flow)で作ることもできますが、既存のクイックアクションの見た目に合わせたいときはLWCを選びます。
画面構成
lightning-progress-indicatorで現在位置を示し、template if:trueでステップごとの中身を出し分けます。
stepsAccountScreenActionSample.html
<template>
<div style="margin-top: 20px">
<lightning-progress-indicator current-step={currentStep}>
<lightning-progress-step label="日付を選択する" value="step1" onclick={handleClickStep}></lightning-progress-step>
<lightning-progress-step label="数値項目を入力する" value="step2" onclick={handleClickStep}></lightning-progress-step>
<lightning-progress-step label="テキスト項目を入力する" value="step3" onclick={handleClickStep}></lightning-progress-step>
</lightning-progress-indicator>
</div>
<template if:true={isStep1}>
<lightning-quick-action-panel header="日付を選択する">
<lightning-layout multiple-rows="true">
<lightning-combobox name="selectMonth"
label="月"
options={monthOptions}
dropdown-alignment="left"
value={month}
required
onchange={month_handleOnChange}>
</lightning-combobox>
</lightning-layout>
<div slot="footer">
<lightning-button variant="neutral" label="キャンセル" onclick={handleClickCancel} class="slds-m-left_x-small"></lightning-button>
<lightning-button variant="brand" label="次へ" onclick={handleClickNext} disabled={isNotSelected} class="slds-m-left_x-small"></lightning-button>
</div>
</lightning-quick-action-panel>
</template>
<template if:true={isStep2}>
<lightning-quick-action-panel header="数値項目を入力する">
<lightning-input type="number" label="数値" value={num} required onchange={num_handleOnChange}></lightning-input>
<div slot="footer">
<lightning-button variant="neutral" label="戻る" onclick={handleClickPrev} class="slds-m-left_x-small"></lightning-button>
<lightning-button variant="brand" label="次へ" onclick={handleClickNext} disabled={isNotSelected} class="slds-m-left_x-small"></lightning-button>
</div>
</lightning-quick-action-panel>
</template>
<template if:true={isStep3}>
<lightning-quick-action-panel header="テキスト項目を入力する">
<lightning-input type="text" label="テキスト" value={text} required onchange={text_handleOnChange}></lightning-input>
<div slot="footer">
<lightning-button variant="neutral" label="戻る" onclick={handleClickPrev} class="slds-m-left_x-small"></lightning-button>
<lightning-button variant="brand" label="作成" onclick={handleClickNext} class="slds-m-left_x-small"></lightning-button>
</div>
<template if:true={executing}>
<lightning-spinner alternative-text="実行中です" variant="brand"></lightning-spinner>
</template>
</lightning-quick-action-panel>
</template>
</template>進捗表示と3つのステップ画面のマークアップです。テンプレートの出し分けだけで、URL遷移はありません。
lightning-progress-indicatorのcurrent-stepは、lightning-progress-stepのvalueと一致させます。値が食い違うと、進捗の見た目と表示中のステップがずれます。
画面の切り替えとレコード作成
stepsAccountScreenActionSample.js
import { api, wire, LightningElement } from "lwc"; import { ShowToastEvent } from "lightning/platformShowToastEvent"; import { CloseActionScreenEvent } from "lightning/actions"; import { CurrentPageReference } from "lightning/navigation"; import { RefreshEvent } from "lightning/refresh"; import getAccountId from "@salesforce/apex/stepsAccountScreenActionSampleController.getAccountId"; import createContact from "@salesforce/apex/stepsAccountScreenActionSampleController.createContact"; export default class StepsAccountScreenActionSample extends LightningElement { month; num; text; executing = false; currentStep = "step1"; @api accountId; @api recordId; @wire(CurrentPageReference) async getStateParameters(currentPageReference) { this.recordId = currentPageReference.state.recordId || null; try { this.accountId = await getAccountId({ recordId: this.recordId }); } catch (error) { this.dispatchEvent( new ShowToastEvent({ title: "エラーが発生しました", message: error.body.message, variant: "error" }) ); } } async handleClickCreate() { this.executing = true; try { const result = await createContact({ accountId: this.accountId, month: Number(this.month), num: this.num, text: this.text }); if (result === "success") { this.dispatchEvent( new ShowToastEvent({ title: "作成が完了しました", variant: "success" }) ); this.dispatchEvent(new RefreshEvent()); } else { this.dispatchEvent( new ShowToastEvent({ title: "エラーが発生しました", variant: "error" }) ); } } catch (error) { this.dispatchEvent( new ShowToastEvent({ title: "エラーが発生しました", message: error.body.message, variant: "error" }) ); } finally { this.executing = false; this.dispatchEvent(new CloseActionScreenEvent()); } } month_handleOnChange(event) { this.month = event.target.value; } num_handleOnChange(event) { this.num = event.target.value; } text_handleOnChange(event) { this.text = event.target.value; } handleClickStep(event) { const currentStep = Number(this.currentStep.replace("step", "")); const nextStep = Number(event.target.value.replace("step", "")); if (nextStep - currentStep === 1) { this.handleClickNext(); } else if (nextStep - currentStep === -1) { this.handleClickPrev(); } } handleClickCancel() { this.dispatchEvent(new CloseActionScreenEvent()); } handleClickNext() { if (this.currentStep === "step1") { if (!this.month) { this.dispatchEvent( new ShowToastEvent({ title: "月を選択してください", variant: "error" }) ); return; } this.currentStep = "step2"; } else if (this.currentStep === "step2") { this.currentStep = "step3"; } else if (this.currentStep === "step3") { this.handleClickCreate(); } } handleClickPrev() { if (this.currentStep === "step2") { this.currentStep = "step1"; } else if (this.currentStep === "step3") { this.currentStep = "step2"; } } get isStep1() { return this.currentStep === "step1"; } get isStep2() { return this.currentStep === "step2"; } get isStep3() { return this.currentStep === "step3"; } get monthOptions() { const options = []; for (let i = 1; i <= 12; i++) { options.push({ label: `${i}月`, value: String(i) }); } return options; } }
画面の切り替えとレコード作成を行うコントローラです。DMLはApex側でロールバック付きにしてあります。
当時の記事の書き方から、3つ直しています。
@trackを外しました。各フィールドはどれも単純な値の再代入で、配列やオブジェクトの内部を書き換えているわけではありません。テンプレートで使うフィールドは既定でリアクティブなので不要です。- 内部のイベントハンドラから
@apiを外しました。@apiは外部から直接呼べる公開APIを作るための修飾子です。ボタンからしか呼ばない処理に付けると、意図せず外部へ公開してしまいます。 eval("$A.get('e.force:refreshView').fire()")をlightning/refreshのRefreshEventに差し替えました。Auraのイベントをevalで無理に呼ぶ書き方で、標準APIではありません。公式ガイドは、RefreshEventをforce:refreshViewの後継として明記しています。このコンポーネントもLightning Web Securityの仮想サンドボックス内で動くので、標準化されていない書き方には頼らないほうが安全です。
Apexコントローラ
stepsAccountScreenActionSampleController.cls
public with sharing class stepsAccountScreenActionSampleController { @AuraEnabled(cacheable=false) public static String getAccountId(Id recordId) { List<Account> accounts = [ SELECT Id FROM Account WHERE Id = :recordId LIMIT 1 ]; String ret = ''; if (accounts.size() > 0) { ret = accounts.get(0).Id; } return ret; } @AuraEnabled(cacheable=false) public static String createContact( Id accountId, Integer month, Integer num, String text ) { Savepoint sp = Database.setSavepoint(); Contact con = new Contact(); con.AccountId = accountId; con.LastName = text; con.Birthdate = Date.newInstance(Date.today().year(), month, 1); con.number__c = num; try { insert con; } catch (Exception e) { Database.rollback(sp); return 'error'; } return 'success'; } }
Savepointで保護したうえで取引先責任者を1件作成します。失敗時はロールバックします。
レコードを作る処理なのでcacheable=falseのままにします。失敗したらDatabase.rollbackで戻す構成です。
メタデータとクイックアクションの配置
stepsAccountScreenActionSample.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>67.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__RecordAction</target> </targets> <targetConfigs> <targetConfig targets="lightning__RecordAction"> <actionType>ScreenAction</actionType> </targetConfig> </targetConfigs> </LightningComponentBundle>
レコードページのアクションとして公開する設定です。apiVersionを67.0へ上げています。
targetをlightning__RecordActionにし、actionTypeをScreenActionにすると、レコードページのアクションから画面付きで呼び出せるコンポーネントとして公開されます。apiVersionは当時の59.0からSummer '26の67.0へ上げました。
配置と確認の手順です。
取引先オブジェクトにアクションを追加する
取引先オブジェクトのボタン・リンク・アクションで新規アクションを作成し、アクションタイプを「Lightning Web コンポーネント」、コンポーネントに本コンポーネントを指定します。
ページレイアウトへアクションを配置する
取引先のページレイアウト編集で、モバイルおよびLightning Experienceのアクション欄へ、作成したアクションをドラッグして保存します。
取引先レコードから動作を確かめる
対象の取引先レコードを開き、追加したアクションを押します。Step1で日付を選び「次へ」、Step2で数値を入力して「次へ」、Step3でテキストを入力して「作成」を押します。
作成結果を確認する
成功のトーストが出て画面が閉じたあと、取引先責任者の関連リストに新しいレコードが増えていることを確かめます。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
currentStepの値とlightning-progress-stepのvalueを食い違わせる | 進捗表示と実際に出ている画面がずれる |
内部専用のメソッドに@apiを付ける | 外部から呼べる公開APIとして意図せず公開される |
単純な値の再代入にまで@trackを付ける | 動作はするが不要。配列・オブジェクトの内部を書き換えるとき専用 |
evalでAuraのイベントを呼ぼうとする | 標準APIではない。lightning/refreshのRefreshEventに直す |
| ステップ番号の文字列比較だけで分岐を作り込む | ステップが増えるほど分岐が複雑になり、抜けが起きやすい |
確認した環境
- 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の
lightning-progress-indicatorコンポーネントリファレンス、LWC開発者ガイドの設定タグ・RefreshView APIのページで、進捗表示と画面更新の仕様を確認しています - ご自身の組織のオブジェクト・項目名に合わせて確かめてください
まとめ
- クイックアクションを複数ステップに分けると、1画面あたりの入力負担が下がります
lightning-progress-indicatorのcurrent-stepは、lightning-progress-stepのvalueと必ず一致させます- 単純な値の再代入には
@trackは不要です。テンプレートで使うフィールドは既定でリアクティブです @apiは外部へ公開する意図があるものだけに付けます。内部専用のハンドラには付けませんevalでAuraのforce:refreshViewを呼ぶ書き方は、lightning/refreshのRefreshEventに直します- apiVersionはSummer '26の67.0へ上げました
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。