zukucode
主にWEB関連の情報を技術メモとして発信しています。

React 操作できるかをAPIから返して権限の分岐を減らす

編集ボタンを出すために、画面ごとに「管理者か」「所有者か」を判定していることがあります。

後から「担当者も編集できる」「停止中は管理者でも編集できない」といった条件が加わると、複数の画面を同時に直す必要が出てきます。

今回は、役割の名前を画面で判定する代わりに、その対象に対して何ができるかをAPIから返す構成を紹介します。

ロール名と操作の可否は同じではない

adminやmemberのような役割をロールと呼びます。同じロールでも、所属する組織や操作対象の状態によって、できることが変わる場合があります。

編集できる条件の例
組織に所属している
かつ、編集権限を持っている
かつ、対象が停止されていない

この条件を各画面に複製すると、サーバー側のルールが変わったときに、古い判定が残りやすくなります。

サーバーで判定した結果をcanEditなどの名前で返せば、画面は表示に必要な結果を利用できます。

操作可否を対象のデータに含める

ProjectDetail.ts
export type ProjectDetail = {
  id: string;
  name: string;
  allowedActions: {
    canEdit: boolean;
    canDelete: boolean;
  };
};

allowedActionsは、その対象について表示上どの操作を案内できるかをまとめた項目です。

利用者全体に1つのcanEditを持たせるのではなく、どのプロジェクトや組織に対する判定なのかを明確にします。同じ利用者でも、対象によって結果は変わり得ます。

画面では結果を使ってボタンを出す

ProjectActions.tsx
import type { ProjectDetail } from './ProjectDetail';

type Props = {
  project: ProjectDetail;
  isSaving: boolean;
  onEdit: () => void;
};

export const ProjectActions = ({ project, isSaving, onEdit }: Props) => (
  <div>
    {project.allowedActions.canEdit && (
      <button type="button" disabled={isSaving} onClick={onEdit}>
        編集
      </button>
    )}
  </div>
);

権限による出し分けと、保存中の二重操作を防ぐ表示を分けています。canEditは操作権限、isSavingは画面の現在の処理状態です。

この例は、データ取得が成功した後に表示する部品です。取得中や失敗時の表示は、親の画面で扱います。

ボタンを隠すか、理由を表示して無効にするかは画面の目的によります。たとえば「担当者へ権限を依頼できる」と案内したいなら、理由付きの表示が役立つこともあります。

取得失敗を権限なしと混同しない

データがまだない場合に、次のように操作を有効にしないこと自体は大切です。

操作を有効にしない初期値
const canEdit = project?.allowedActions.canEdit ?? false;

ここでのprojectは、未取得ならundefinedになる取得結果です。

ただし、通信エラーまで「あなたには権限がありません」と表示すると、利用者は何をすればよいかわかりません。

状態表示の例
取得中読み込み中。操作は有効にしない
通信失敗取得エラーと再試行の案内
取得成功・操作不可閲覧用の表示、必要なら権限の説明
取得成功・操作可能編集ボタンを表示

falseの初期値を使うことと、エラーの原因を同じに扱うことは別です。

更新APIは現在の権限を改めて確認する

画面へ返したcanEditは、取得した時点の結果です。画面を開いた後に権限が取り消される場合もあります。

また、ブラウザー上の値やボタンの表示は変更できます。更新APIへcanEdit: trueを送り、それを信用して許可する設計にはしません。

更新APIでは、認証済みの利用者と操作対象を使い、現在の権限を確認します。対象の情報に基づく認可については、Microsoft Learnのリソースベース認可の説明も参照してください。

表示と更新の役割
取得API:利用者と対象から操作可否を計算し、画面へ返す
画面:返された結果で操作を案内する
更新API:現在の権限と対象の状態を確認して実行する

取得API自体も、そのデータを閲覧してよいかを確認してから返します。操作可否を含めることは、閲覧権限の検証を不要にするものではありません。

判定ルールをサーバーでも二重管理しない

表示用のcanEditと、更新時の認可で別々に同じ条件を書くと、サーバー内でもずれる可能性があります。

共通の認可処理やポリシーを呼べるようにし、表示時も更新時も、その時点の利用者と対象を渡す方法があります。

ただし、判定に必要な情報の取得量にも注意します。一覧の各行で何度もDBへ問い合わせるなら、まとめて必要な情報を取得する設計を検討します。画面の分岐を減らした代わりに、一覧取得が極端に重くならないようにします。

小さな画面に必ず必要な構成ではない

管理者だけが使う単純な画面なら、ロール判定の方がわかりやすい場合もあります。

対象ごとの権限や状態の組み合わせが増え、複数画面で同じ複雑な分岐を書いているときに、操作単位の結果を返す方法が役立ちます。

導入後は、別の組織へ切り替えたときやログアウト時に、古い操作可否のキャッシュを使い続けないことも確認します。

更新後にキャッシュを再取得する方法

ボタンの表示を決める処理を減らしつつ、実行の許可はサーバーで確認する。この分担を保つことが、権限変更に追従しやすい画面を作るポイントです。


関連記事

  • TypeScript i18nextの動的な翻訳キーを抽出対象に含める

    i18nextでステータスに応じた文言を表示する場合、翻訳キーを動的に切り替えることがあります。ただし、実行時に正しく表示できることと、翻訳キーの抽出ツールが使用箇所を検出できることは別です。今回は、...


  • React useStateで入力フォームを作成する

    Reactでテキストボックスに入力した値を使用するには、useStateで値を保持します。今回は名前とメールアドレスを入力し、送信ボタンを押すと入力内容を表示するフォームを作成します。この例では入力値...


  • React useStateで一覧の追加と削除を行う

    Reactで一覧を表示するときは、配列をuseStateへ保存できます。今回は簡単な買い物リストを作り、項目の追加と削除を行います。配列のpushやspliceで既存のstateを直接変更するのではな...


  • React useRefで入力欄にフォーカスを当てる

    入力フォームを開いた直後や、入力エラーが出たときに、特定の入力欄へフォーカスしたいことがあります。DOMの要素自体を参照するにはuseRefを使います。currentは初回描画中などにnullになり得...


  • React useMemoで重い計算結果を再利用する

    一覧の絞り込みや並べ替えを描画のたびに行うと、データ量や計算内容によっては画面操作が遅くなります。useMemoは、依存する値が変わらない間、以前の計算結果を再利用します。productsまたはque...


  • React useEffectで無限ループが発生するときに確認すること

    ReactのuseEffectを利用したときに無限ループが発生してしまうことがあります。特に注意したいのが、ESLintのreact-hooks/exhaustive-depsで表示された警告をUpd...


  • React useEffectの後片付けでイベント登録を解除する

    画面の幅が変わったときに表示を更新したい場合、ブラウザのresizeイベントを登録できます。登録したままにすると、コンポーネントが不要になったあとも処理が残るため、useEffectの後片付けを用意し...


  • React 関数コンポーネントにプロパティを設定する

    React+TypeScriptで作成した関数コンポーネントにプロパティを設定する方法を紹介します。以下は送信ボタンのコンポーネントを作成しています。isSubmittingというプロパティを用意して...


  • Reactで兄弟コンポーネントの状態を共有する

    検索欄に入力した文字列を、別の一覧コンポーネントでも使いたい場合があります。兄弟同士で別々にstateを持つと値がずれるため、共通の親にstateを置きます。入力欄は値を受け取り、変更を親へ通知します...


  • React useStateでselectの選択値を取得する

    Reactでプルダウンの選択値を扱うには、selectのvalueとonChangeを使用します。DOMから取得するvalueは、数値の選択肢を表示していても文字列です。選択値をそのまま識別子として扱...