【React】コンテクストを使った props を用いない関数一つで呼び出せる通知用コンポーネント例

コンテクスト – React
 コンテクストは React の簡易なグローバル管理機能です。 props を経由せずに値を操作、反映できることによって多様な props のリレーを行うことなく各所で同一の値を扱えます。これを利用することで props リレーをすることなく関数一つで通知なりなんなりを呼び出せます。
 デモは次です。

 各ボタンコンポーネントでは props を用いていませんが、ボタンを押すとグローバル領域の通知用スナックバーに通知が追加されます。呼び出し側のソースコードは次です。

// 通知追加ボタンコンポーネント クラスコンポーネント版
import React from "react";
import { NotifyMessagesContext } from "./NotifyMessagesContext.jsx";

export default class ClassComponent extends React.Component {
  constructor(props, context) {
    // context を元クラスである React.Component に渡すとよしなにやってくれます
    super(props, context);
    
    this.pushMessage = () => {
      // 実行はこの context に引っ付いた関数を呼び出すのみ
      this.context.pushMessage("クラスコンポーネントからの通知");
    };
  }
  render() {
    return (
      <div>
        <button className="push-notify-btn" onClick={this.pushMessage}>
          通知追加 in クラスコンポーネント
        </button>
      </div>
    );
  }
}
// どの context を使うかの宣言。これがないとクラスコンポーネントはコンテクストを使えません
ClassComponent.contextType = NotifyMessagesContext;
// 通知追加ボタンコンポーネント 関数コンポーネント版
import React from "react";
import { useNotifyMessages } from "./NotifyMessagesContext.jsx";

export default function FunctionComponent() {
  // import した useNotifyMessages から pushMessage を呼び出し
  const { pushMessage } = useNotifyMessages();
  // ボタンを押したら pushMessage を文字列を引数にして呼び出すだけ
  return (
    <div>
      <button
        className="push-notify-btn"
        onClick={() => pushMessage("関数コンポーネントからの通知")}
      >
        通知追加 in 関数コンポーネント
      </button>
    </div>
  );
}

 呼び出し側はコンテクストから一つの関数を呼び出し、それを実行しているのみです。グローバルなところから呼び出すため props のリレーも不要です。これだけで立派な通知になるので再利用が容易です。
 これを実現しているコンテクストのコードは次です。 Matterial UI のスナックバーを用いていますが、原理的には他のコンポーネントでも可能です。
Snackbar API – Material-UI

import React, { useContext, useState } from "react";
import Snackbar from "@material-ui/core/Snackbar";
import IconButton from "@material-ui/core/IconButton";
import CloseIcon from "@material-ui/icons/Close";
// どの様なコンテクストかを定義.
// キーを持つメッセージ群 と メッセージを追加する関数
export const NotifyMessagesContext = React.createContext({
  messages: {},
  pushMessage: msg => {}
});

// 各関数コンポーネントで使う
export const useNotifyMessages = () => useContext(NotifyMessagesContext);
// ルートコンポーネントで使う
export const NotifyMessagesProvider = NotifyMessagesComponent;

// メッセージのインデックス。関数コンポーネントでは保持できないので外部に定義
let msgIndex = 0;
/**
 * スナックバーに送られてきたメッセージを表示する
 * @constructor
 */
function NotifyMessagesComponent(props) {
  // メッセージを state で持つ
  const [messages, setMessages] = useState({});
  // メッセージの追加関数
  const pushMessage = newMessage => {
    const newMsgIndex = msgIndex + 1;
    // 送られてきたメッセージに時刻の接頭辞をつける
    const formattedNewMessages = `[${new Date().toISOString()}]:${newMessage}`;
    // state にメッセージを追加
    setMessages({ ...messages, [`${newMsgIndex}`]: formattedNewMessages });
    msgIndex++;
  };
  
  // メッセージを削除する関数
  const deleteMessage = (deleteKey, reason = "") => {
    if (reason === "clickaway") {
      // スナックバーの外をクリックした時は閉じない
      return;
    }
    
    const newMessages = {};
    // 削除対象以外のキーを持つメッセージを残した部分を newMessages に入れて state にセット
    Object.keys(messages)
      .filter(msgKey => msgKey !== deleteKey)
      .forEach(msgKey => (newMessages[msgKey] = messages[msgKey]));
    setMessages(newMessages);
  };

  // スナックバーを連続して表示する部分. 5000ms で自動で消える
  // NotifyMessagesContext.Provider と props.children が重要。他は Material UI関連の定義
  return (
    <NotifyMessagesContext.Provider value={{ messages, pushMessage }}>
      {Object.keys(messages).map((msgKey, index) => (
        <Snackbar
          key={msgKey}
          anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
          style={{ bottom: `${24 + 72 * index}px` }}
          autoHideDuration={5000}
          open={!!messages[msgKey]}
          onClose={(event, reason) => deleteMessage(msgKey, reason)}
          message={messages[msgKey]}
          action={
            <IconButton
              size="small"
              aria-label="close"
              color="inherit"
              onClick={() => deleteMessage(msgKey)}
            >
              <CloseIcon fontSize="small" />
            </IconButton>
          }
        />
      ))}
      {props.children}
    </NotifyMessagesContext.Provider>
  );
}

 コンテクストのプロバイダで囲った中で props.children を呼びます。こうするとこのコンポーネントで囲った中ではどこでもコンテクストが使えます。このコンポーネントを呼び出すのは大体、ルートコンポーネントでこれは次のコードです。

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
import { NotifyMessagesProvider } from "./NotifyMessagesContext";

const rootElement = document.getElementById("root");
// import した NotifyMessagesProvider を使うだけ
ReactDOM.render(
  <React.StrictMode>
    <NotifyMessagesProvider>
      <App />
    </NotifyMessagesProvider>
  </React.StrictMode>,
  rootElement
);

 このコンテクストの使い方の応用で全画面ローディング、エラーハンドリング、ページ遷移時のメッセージなど様々なグローバルなものを定義できます。
 

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG