mcpField
I
implementing-ui-bundle-file-upload

implementing-ui-bundle-file-upload

github.com/forcedotcom開発1.1k3.5k 回インストール1 日前に更新
リポジトリ

implementing-ui-bundle-file-upload について

Salesforce React UI bundleでAPIを使ったファイルアップロードを実装するスキルです。独自のアップロード画面を構築する開発者向けで、進捗追跡やキャンセル、レコードへの即時・後続の関連付けに対応します。

主な機能

  1. 01コールバックによる進捗追跡
  2. 02Salesforceレコードへの関連付け
  3. 03アップロード後のContentVersion作成
  4. 04AbortControllerによるキャンセル
  5. 05現在のSalesforceユーザーIDの取得

活用シーン

  1. 01既存レコードへのファイル添付
  2. 02レコード作成前のファイルアップロード
  3. 03独自のアップロード・進捗画面の構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/forcedotcom/sf-skills --skill implementing-ui-bundle-file-upload

インストール後、Claude Code で/implementing-ui-bundle-file-uploadを入力して読み込まれたか確認します。

詳細

ファイルアップロード API(ワークフロー)

React UI バンドルでファイルアップロード機能が必要な場合は、このワークフローに従ってください。この機能が提供するのは API のみ です。提供される API を使って、UI コンポーネントはご自身で構築する必要があります。

重要:このパッケージは API のみです

このパッケージがエクスポートするのは プログラムから利用する API であり、React コンポーネントやフックではありません。以下のように利用します。

  • upload() 関数を使って、進行状況を追跡しながらファイルをアップロードする
  • 独自のカスタム UI(ファイル入力、ドロップゾーン、進行状況バーなど)を構築する
  • onProgress コールバックを通じてアップロードの進行状況を追跡する

次のことはしないでください。

  • <FileUpload /> のような構築済みコンポーネントがあると思わないでください。エクスポートされていません
  • useFileUpload のような React フックをインポートしようとしないでください。エクスポートされていません
  • ドロップゾーンコンポーネントを探さないでください。エクスポートされていません

ソースコードにはデモ用の参考コンポーネントが含まれていますが、インポートして利用することはできません。これらを例として、独自の UI を構築してください。

1. パッケージをインストールする

npm install @salesforce/ui-bundle-template-feature-react-file-upload

依存関係は自動的にインストールされます。

  • @salesforce/ui-bundle(API クライアント)
  • @salesforce/sdk-data(データ SDK)

2. 3 つのアップロードパターンを理解する

パターン A:基本的なアップロード(レコードへの関連付けなし)

ファイルを Salesforce にアップロードし、各ファイルの contentBodyId を取得します。ContentVersion レコードは作成されません。

使用する場面:

  • 先にファイルをアップロードし、後からレコードを作成または関連付ける場合
  • レコードがまだ存在しない状態で複数ステップのフォームを作成する場合
  • レコードへの関連付けを後回しにする場合
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

const results = await upload({
  files: [file1, file2],
  onProgress: (progress) => {
    console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
  },
});

// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: undefined (no record linked)
パターン B:レコードへの即時関連付けを伴うアップロード

ファイルをアップロードし、ContentVersion レコードを作成して、既存の Salesforce レコードにすぐに関連付けます。

使用する場面:

  • レコード(Account、Opportunity、Case など)がすでに存在する場合
  • ファイルをすぐにレコードに添付したい場合
  • アップロードと添付を直接行う場合
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

const results = await upload({
  files: [file1, file2],
  recordId: "001xx000000yyyy", // Existing record ID
  onProgress: (progress) => {
    console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
  },
});

// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: "068..." (linked to record)
パターン C:レコードへの関連付けを後から行う(レコード作成フロー)

レコードを指定せずにファイルをアップロードし、レコードの作成後に関連付けます。

使用する場面:

  • 「添付ファイル付きでレコードを作成する」フォームを構築する場合
  • フォームの送信までレコードが存在しない場合
  • 最終的なレコード ID がわかる前にファイルをアップロードする必要がある場合
import {
  upload,
  createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";

// Step 1: Upload files (no recordId)
const uploadResults = await upload({
  files: [file1, file2],
  onProgress: (progress) => console.log(progress),
});

// Step 2: Create the record
const newRecordId = await createRecord(formData);

// Step 3: Link uploaded files to the new record
for (const file of uploadResults) {
  const contentVersionId = await createContentVersion(
    new File([""], file.fileName),
    file.contentBodyId,
    newRecordId,
  );
}

3. カスタム UI を構築する

このパッケージが提供するのはバックエンドです。フロントエンドはご自身で構築します。以下に最小限の例を示します。

import {
  upload,
  type FileUploadProgress,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
import { useState } from "react";

function CustomFileUpload({ recordId }: { recordId?: string }) {
  const [progress, setProgress] = useState<Map<string, FileUploadProgress>>(new Map());

  const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files || []);

    await upload({
      files,
      recordId,
      onProgress: (fileProgress) => {
        setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
      },
    });
  };

  return (
    <div>
      <input type="file" multiple onChange={handleFileSelect} />

      {Array.from(progress.entries()).map(([fileName, fileProgress]) => (
        <div key={fileName}>
          {fileName}: {fileProgress.status} - {fileProgress.progress}%
          {fileProgress.error && <span>Error: {fileProgress.error}</span>}
        </div>
      ))}
    </div>
  );
}

4. アップロードの進行状況を追跡する

onProgress コールバックは、各ファイルの処理段階に応じて複数回呼び出されます。

ステータスタイミング進行状況の値
"pending"ファイルがアップロード待ちになったとき0
"uploading"アップロード中(XHR)0-100(パーセント)
"processing"ContentVersion の作成中(recordId が指定された場合)0
"success"アップロード完了時100
"error"アップロード失敗時0

必ず視覚的なフィードバックを表示してください。

  • ファイル名を表示する
  • 現在のステータスを表示する
  • ステータスが "uploading" の場合は進行状況バーを表示する
  • ステータスが "error" の場合はエラーメッセージを表示する

5. アップロードをキャンセルする(任意)

AbortController を使って、ユーザーがアップロードをキャンセルできるようにします。

const abortController = new AbortController();

const handleUpload = async (files: File[]) => {
  try {
    await upload({
      files,
      signal: abortController.signal,
      onProgress: (progress) => console.log(progress),
    });
  } catch (error) {
    console.error("Upload cancelled or failed:", error);
  }
};

const cancelUpload = () => {
  abortController.abort();
};

6. 現在のユーザーに関連付ける(特別なケース)

ユーザー自身のプロフィールまたは個人ライブラリにファイルをアップロードする場合:

import {
  upload,
  getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";

const userId = await getCurrentUserId();
await upload({ files, recordId: userId });

API リファレンス

upload(options)

進行状況を追跡しながら一連の処理を完了する、メインのアップロード API です。

interface UploadOptions {
  files: File[];
  recordId?: string | null; // If provided, creates ContentVersion
  onProgress?: (progress: FileUploadProgress) => void;
  signal?: AbortSignal; // Optional cancellation
}

interface FileUploadProgress {
  fileName: string;
  status: "pending" | "uploading" | "processing" | "success" | "error";
  progress: number; // 0-100 for uploading, 0 for other states
  error?: string;
}

interface FileUploadResult {
  fileName: string;
  size: number;
  contentBodyId: string; // Always available
  contentVersionId?: string; // Only if recordId was provided
}

戻り値: Promise<FileUploadResult[]>

createContentVersion(file, contentBodyId, recordId)

以前にアップロードしたファイルから、ContentVersion レコードを手動で作成します。

async function createContentVersion(
  file: File,
  contentBodyId: string,
  recordId: string,
): Promise<string | undefined>;

パラメーター:

  • file — File オブジェクト(名前などのメタデータに使用)
  • contentBodyId — 以前のアップロードで取得した ContentBody ID
  • recordId — FirstPublishLocationId に使用するレコード ID

戻り値: 成功した場合は ContentVersion ID

getCurrentUserId()

現在のユーザーの Salesforce ID を取得します。

async function getCurrentUserId(): Promise<string>;

戻り値: 現在のユーザー ID

一般的な UI パターン

ボタン付きファイル入力
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />
ドラッグ&ドロップ領域

ネイティブイベントを使って、独自のドロップゾーンを作成します。

function DropZone({ onDrop }: { onDrop: (files: File[]) => void }) {
  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault();
    const files = Array.from(e.dataTransfer.files);
    onDrop(files);
  };

  return (
    <div
      onDrop={handleDrop}
      onDragOver={(e) => e.preventDefault()}
      style={{ border: "2px dashed #ccc", padding: "2rem" }}
    >
      Drop files here
    </div>
  );
}
進捗バー
{
  progress.status === "uploading" && (
    <div style={{ width: "100%", background: "#eee" }}>
      <div
        style={{
          width: `${progress.progress}%`,
          background: "#0176d3",
          height: "8px",
        }}
      />
    </div>
  );
}

エージェント向け判断ツリー

ユーザーがファイルアップロード機能を求めている場合:

  1. レコードのコンテキストについて確認します:

    • 「アップロードしたファイルを特定のレコードにリンクしますか。それとも、まずアップロードして後からリンクしますか?」
  2. 回答に応じて:

    • 既存のレコードにリンクする → recordId を指定してパターン B を使用します
    • 先にアップロードして後からリンクする → パターン A(recordId なし)を使用し、その後、リンクにはパターン C を使用します
    • 現在のユーザーにリンクする → getCurrentUserId() を使用してパターン B を使用します
  3. UI を構築します:

    • ファイル入力またはドロップゾーンを作成します(パッケージには含まれていません)
    • 各ファイルの進捗表示(ステータスと進捗バー)を追加します
    • UI でエラーを処理します
  4. 実装をテストします:

    • 進捗コールバックが正しく呼び出されることを確認します
    • contentBodyId が返されることを確認します
    • recordId を指定した場合は、contentVersionId が返されることを確認します

リファレンス実装

パッケージには、src/features/fileupload/ にリファレンス実装が含まれています。

  • FileUpload.tsx — ドロップゾーンとダイアログを備えた完全なコンポーネント
  • FileUploadDialog.tsx — 進捗追跡ダイアログ
  • FileUploadDropZone.tsx — ドラッグ&ドロップ領域
  • useFileUpload.ts — 状態管理用の React フック

これらはエクスポートされていませんが、例として参照できます。独自の UI を構築するパターンを理解するには、ソースファイルをお読みください。

トラブルシューティング

アップロードが CORS エラーで失敗する場合:

  • UI バンドルが Salesforce に正しくデプロイされているか、localhost で実行されていることを確認します
  • 組織の CORS 設定でオリジンが許可されているか確認します

進捗が更新されない場合:

  • onProgress コールバックが指定されていることを確認します
  • コールバック関数が React の状態を正しく更新しているか確認します

ContentVersion が作成されない場合:

  • upload() 関数に recordId が指定されていることを確認します
  • レコード ID が有効で、組織内に存在することを確認します
  • ユーザーに ContentVersion レコードを作成する権限があることを確認します

ファイルはアップロードされるが、レコードに表示されない場合:

  • recordId が正しいことを確認します
  • ContentVersion が作成されていることを確認します(結果に contentVersionId があるか確認します)
  • ユーザーがそのレコード上のファイルを表示するアクセス権を持っていることを確認します

次のことはしないでください

  • ❌ XHR/fetch によるアップロード処理を一から構築しないでください — upload() API を使用します
  • ❌ <FileUpload /> コンポーネントをインポートしようとしないでください — エクスポートされていません
  • ❌ useFileUpload フックをインポートしようとしないでください — エクスポートされていません
  • ❌ この機能がある場合に、サードパーティ製のファイルアップロードライブラリを使用しないでください
  • ❌ 進捗追跡を省略しないでください — 常にユーザーに進捗を伝えます
  • ❌ エラーを無視しないでください — 必ずエラーメッセージを処理して表示します

コメント