

# IVS Chat Client Messaging SDK: React Native チュートリアルパート 2: メッセージとイベント
<a name="chat-sdk-react-tutorial-messages-events"></a>

本チュートリアルのパート 2 (最後のパート) は、複数のセクションに分かれています。

1. [チャットメッセージイベントへのサブスクライブ](#chat-react-messages-events-subscribe)

1. [受信済みメッセージの表示](#chat-react-messages-events-show)

   1.  [メッセージコンポーネントの作成](#chat-react-messages-create-component)

   1. [現在のユーザーにより送信されたメッセージの認識](#chat-react-messages-recognize)

   1. [チャットメッセージのリストのレンダリング](#chat-react-messages-render-list)

1. [チャットルームでアクションを実行する](#chat-react-messages-events-room-actions)

   1. [メッセージの送信](#chat-react-room-actions-sending-message)

   1. [メッセージの削除](#chat-react-room-actions-deleting-message)

1. [次のステップ](#chat-react-messages-events-next-steps)

**注**: JavaScript と TypeScript のコード例が同じ内容である場合は、共通の例として示しています。

## 前提条件
<a name="chat-react-messages-events-prerequisite"></a>

このチュートリアルのパート 1 である「[チャットルーム](chat-sdk-react-tutorial-chat-rooms.md)」を完了してから、このパートに進んでください。

## チャットメッセージイベントへのサブスクライブ
<a name="chat-react-messages-events-subscribe"></a>

チャットルームでイベントが発生した際、`ChatRoom` インスタンスはイベントを使用して通信を行います。チャットによるエクスペリエンスを開始するには、ルーム内で、そこに接続しているユーザーに対し、他のユーザーからメッセージを送信されたことを知らせる必要があります。

このためにユーザーは、チャットメッセージイベントをサブスクライブします。メッセージ/イベントごとに、作成済みのメッセージリストを更新する方法については、この後半で説明します。

`App` の `useEffect` フック内で、すべてのメッセージイベントにサブスクライブします。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

useEffect(() => {
  // ...
  const unsubscribeOnMessageReceived = room.addListener('message', (message) => {});

  return () => {
    // ...
    unsubscribeOnMessageReceived();
  };
}, []);
```

## 受信済みメッセージの表示
<a name="chat-react-messages-events-show"></a>

メッセージの受信は、チャットエクスペリエンスにとって中心的な要素です。Chat JS SDKを使用してコードを作成することで、チャットルームに接続している他のユーザーからのイベントを簡単に受信できます。

後半部分で、ここで作成したコンポーネントを活用してチャットルームでアクションを実行する方法を説明します。

`App` で、`messages` という名前の `ChatMessage` 配列型を使用して、`messages` という名前の状態を定義します。

------
#### [ TypeScript ]

```
// App.tsx

// ...

import { ChatRoom, ChatMessage, ConnectionState } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);

  //...
}
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

import { ChatRoom, ConnectionState } from 'amazon-ivs-chat-messaging';

export default function App() {
  const [messages, setMessages] = useState([]);

  //...
}
```

------

次に、`message` リスナー関数内で、`messages` 配列に `message` を追加します。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
  setMessages((msgs) => [...msgs, message]);
});

// ...
```

以下に、受信したメッセージを表示するタスクを順を追って説明します。

1.  [メッセージコンポーネントの作成](#chat-react-messages-create-component)

1. [現在のユーザーにより送信されたメッセージの認識](#chat-react-messages-recognize)

1. [チャットメッセージのリストのレンダリング](#chat-react-messages-render-list)

### メッセージコンポーネントの作成
<a name="chat-react-messages-create-component"></a>

`Message` コンポーネントにより、チャットルームで受信したメッセージ内容のレンダリングが行われます。このセクションでは、個々のチャットメッセージを `App` 内でレンダリングするための、メッセージコンポーネントを作成します。

`src` ディレクトリで `Message` という名前の新しいファイルを作成します。このコンポーネントに `ChatMessage` 型を渡し、さらに `ChatMessage` プロパティからの `content` 文字列を渡すことで、チャットルームのメッセージリスナーから受信したメッセージテキストを表示します。プロジェクトナビゲーターで、`Message` に移動します。

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  return (
    <View style={styles.root}>
      <Text>{message.sender.userId}</Text>
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export const Message = ({ message }) => {
  return (
    <View style={styles.root}>
      <Text>{message.sender.userId}</Text>
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
});
```

------

**ヒント**: このコンポーネントを使用して、メッセージ行に表示するさまざまなプロパティ (アバター URL、ユーザー名、メッセージ送信時のタイムスタンプなど) を保存できます。

### 現在のユーザーにより送信されたメッセージの認識
<a name="chat-react-messages-recognize"></a>

現在のユーザーから送信されたメッセージを認識するために、そのユーザーの `userId` を格納する React コンテキストを作成するように、コードを変更します。

`src` ディレクトリに、`UserContext` という名前で新しいファイルを作成します。

------
#### [ TypeScript ]

```
// UserContext.tsx

import React from 'react';

const UserContext = React.createContext<string | undefined>(undefined);

export const useUserContext = () => {
  const context = React.useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

export const UserProvider = UserContext.Provider;
```

------
#### [ JavaScript ]

```
// UserContext.jsx

import React from 'react';

const UserContext = React.createContext(undefined);

export const useUserContext = () => {
  const context = React.useContext(UserContext);

  if (context === undefined) {
    throw new Error('useUserContext must be within UserProvider');
  }

  return context;
};

export const UserProvider = UserContext.Provider;
```

------

注: ここでは、`useState` フックを使用して `userId` 値を保存しています。将来的には、ユーザーコンテキストの変更や、ログインのためにも `setUserId` を使用できます。

次に、`tokenProvider` に渡された最初のパラメータの `userId` を、先に作成済みのコンテキストにより置き換えます。以下に示すように、トークンプロバイダーに `SEND_MESSAGE` 権限を追加してください。これは、メッセージを送信するために必要です。

------
#### [ TypeScript ]

```
// App.tsx

// ...

import { useUserContext } from './UserContext';

// ...


export default function App() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const userId = useUserContext();
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']),
      }),
  );

  // ...
}
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

import { useUserContext } from './UserContext';

// ...


export default function App() {
  const [messages, setMessages] = useState([]);
  const userId = useUserContext();
  const [room] = useState(
    () =>
      new ChatRoom({
        regionOrUrl: process.env.REGION,
        tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE']),
      }),
  );

  // ...
}
```

------

`Message` コンポーネント内では、既に作成済みの `UserContext` を使用して `isMine` 変数を宣言し、送信者の `userId` とコンテキストからの `userId` を照合し、現在のユーザーにさまざまなスタイルのメッセージを適用します。

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

type Props = {
  message: ChatMessage;
}

export const Message = ({ message }: Props) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export const Message = ({ message }) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <Text style={styles.textContent}>{message.content}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------

### チャットメッセージのリストのレンダリング
<a name="chat-react-messages-render-list"></a>

ここで、`FlatList` および `Message` コンポーネントを使用してメッセージを一覧表示します。

------
#### [ TypeScript ]

```
// App.tsx

// ...

const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => {
  return (
    <Message key={item.id} message={item} />
  );
}, []);

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <FlatList inverted data={messages} renderItem={renderItem} />
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const renderItem = useCallback(({ item }) => {
  return (
    <Message key={item.id} message={item} />
  );
}, []);

return (
  <SafeAreaView style={styles.root}>
    <Text>Connection State: {connectionState}</Text>
    <FlatList inverted data={messages} renderItem={renderItem} />
    <View style={styles.messageBar}>
      <MessageInput value={messageToSend} onMessageChange={setMessageToSend} />
      <SendButton disabled={isSendDisabled} onPress={onMessageSend} />
    </View>
  </SafeAreaView>
);

// ...
```

------

以上で、チャットルームで受信したメッセージを `App` でレンダリングする準備ができました。作成したコンポーネントを活用して、チャットルーム内のアクションを実行する方法について、この後で説明します。

## チャットルームでアクションを実行する
<a name="chat-react-messages-events-room-actions"></a>

チャットルームに対する主な操作例としては、メッセージの送信や、モデレーターのアクション実行などが挙げられます。ここでは、さまざまな ChatRequest オブジェクトを使用し、メッセージの送信や削除、他のユーザーの接続の切断といった一般的なアクションを Chatterbox 内で実行する方法を説明します。

チャットルームのアクションは、すべて共通のパターンに従っており、それぞれ対応するリクエストオブジェクトを持っています。各リクエストは、それが確認された際に、対応するレスポンスオブジェクトを受け取ります。

チャットトークンの作成時に適切な権限が付与されているユーザーであれば、チャットルームで実行できるリクエストを確認するためのアクションを、対応するリクエストオブジェクトを使用して正常に実行できます。

以下で、[メッセージを送信](#chat-react-room-actions-sending-message)する方法と、[メッセージを削除](#chat-react-room-actions-deleting-message)する方法について説明します。

### メッセージの送信
<a name="chat-react-room-actions-sending-message"></a>

`SendMessageRequest` クラスにより、チャットルームでのメッセージ送信が有効化されます。ここでは、「[メッセージ入力の作成](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-message-input)」(このチュートリアルのパート 1) で作成したコンポーネントを使用して、メッセージリクエストを送信するように `App` を変更します。

まず、`isSending` という名前を付けた新しいブール値のプロパティを、`useState` フックで定義します。この新しいプロパティで `isSendDisabled` 定数を使用すると、`button` 要素の無効状態を切り替えることができます。`SendButton` のイベントハンドラーで `messageToSend` の値をクリアし、`isSending` を true に設定します。

API 呼び出しはこのボタンにより実行されるため、`isSending` ブール値を追加することで、リクエストが完了するまで `SendButton` でのユーザー操作を無効にし、複数の API 呼び出しが同時に発生するのを防ぐことができます。

注: メッセージの送信は、上記の「[現在のユーザーにより送信されたメッセージの認識](#chat-react-messages-recognize)」にあるように、トークンプロバイダーに `SEND_MESSAGE` 権限を追加した場合にのみ機能します。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const [isSending, setIsSending] = useState(false);

// ...

const onMessageSend = () => {
  setIsSending(true);
  setMessageToSend('');
};

// ...

const isSendDisabled = connectionState !== 'connected' || isSending;

// ...
```

新しい `SendMessageRequest` インスタンスを作成してリクエストを準備し、メッセージの内容をコンストラクターに渡してます。`isSending` および `messageToSend` の状態を設定したら、`sendMessage` メソッドを呼び出してリクエストをチャットルームに送信します。最後に、リクエストの確認または拒否を受け取った時点で、`isSending` フラグをクリアします。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...
import { ChatRoom, ConnectionState, SendMessageRequest } from 'amazon-ivs-chat-messaging'
// ...

const onMessageSend = async () => {
  const request = new SendMessageRequest(messageToSend);
  setIsSending(true);
  setMessageToSend('');

  try {
    const response = await room.sendMessage(request);
  } catch (e) {
    console.log(e);
    // handle the chat error here...
  } finally {
    setIsSending(false);
  }
};

// ...
```

ここで、Chatterbox を実行してみます。`MessageBar` を使用してメッセージの下書きを作成し、`SendButton` をタップしてメッセージを送信します。先に作成済みの `MessageList` 内にレンダリングされた、送信済みメッセージが表示されるはずです。

### メッセージの削除
<a name="chat-react-room-actions-deleting-message"></a>

チャットルームからメッセージを削除するには、適切な権限が必要です。これらの権限は、チャットルームへの認証時に使用するチャットトークンの初期化中に付与されます。このセクションでは、「[ローカル認証/認可サーバーのセットアップ](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-auth-server)」(このチュートリアルのパート 1) にある `ServerApp` により、モデレーターの機能を指定しています。この処理は、「[トークンプロバイダーの作成](chat-sdk-react-tutorial-chat-rooms.md#chat-react-rooms-token-provider)」(同じくパート 1) で作成した `tokenProvider` オブジェクトを使用して、アプリ内で実行されます。

ここでは、`Message` を変更し、メッセージを削除する関数を追加します。

まず、`App.tsx` を開き `DELETE_MESSAGE` の機能を追加します。 (`capabilities` は `tokenProvider` 関数の 2 番目のパラメータです。)

注: これにより、生成されたチャットトークンに関連付けられているユーザーがチャットルーム内のメッセージを削除できることが、`ServerApp` から IVS Chat API に対し伝達されます。実際には、サーバーアプリのインフラストラクチャでユーザーの権限を管理するためのバックエンドロジックは、さらに複雑なものになるでしょう。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const [room] = useState(() =>
    new ChatRoom({
      regionOrUrl: process.env.REGION,
      tokenProvider: () => tokenProvider(userId, ['SEND_MESSAGE', 'DELETE_MESSAGE']),
    }),
);

// ...
```

以降のステップでは、削除ボタンを表示するために、`Message` を変更していきます。

文字列をパラメータの 1 つとして受け取り `Promise` を返す新しい関数を、`onDelete` の名前で定義します。文字列パラメータには、コンポーネントのメッセージ ID を渡します。

------
#### [ TypeScript ]

```
// Message.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export type Props = {
  message: ChatMessage;
  onDelete(id: string): Promise<void>;
};

export const Message = ({ message, onDelete }: Props) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;
  const handleDelete = () => onDelete(message.id);

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <View style={styles.content}>
        <Text style={styles.textContent}>{message.content}</Text>
        <TouchableOpacity onPress={handleDelete}>
          <Text>Delete<Text/>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  content: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------
#### [ JavaScript ]

```
// Message.jsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useUserContext } from './UserContext';

export const Message = ({ message, onDelete }) => {
  const userId = useUserContext();

  const isMine = message.sender.userId === userId;
  const handleDelete = () => onDelete(message.id);

  return (
    <View style={[styles.root, isMine && styles.mine]}>
      {!isMine && <Text>{message.sender.userId}</Text>}
      <View style={styles.content}>
        <Text style={styles.textContent}>{message.content}</Text>
        <TouchableOpacity onPress={handleDelete}>
          <Text>Delete<Text/>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    backgroundColor: 'silver',
    padding: 6,
    borderRadius: 10,
    marginHorizontal: 12,
    marginVertical: 5,
    marginRight: 50,
  },
  content: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  textContent: {
    fontSize: 17,
    fontWeight: '500',
    flexShrink: 1,
  },
  mine: {
    flexDirection: 'row-reverse',
    backgroundColor: 'lightblue',
  },
});
```

------

次に、`FlatList` コンポーネントに加えられた最新の変更を反映するように `renderItem` を更新します。

`App` の中で `handleDeleteMessage` という名前の関数を定義し、それを `MessageList onDelete` プロパティに渡します。

------
#### [ TypeScript ]

```
// App.tsx

// ...

const handleDeleteMessage = async (id: string) => {};

const renderItem = useCallback<ListRenderItem<ChatMessage>>(({ item }) => {
  return (
    <Message key={item.id} message={item} onDelete={handleDeleteMessage} />
  );
}, [handleDeleteMessage]);

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const handleDeleteMessage = async (id) => {};

const renderItem = useCallback(({ item }) => {
  return (
    <Message key={item.id} message={item} onDelete={handleDeleteMessage} />
  );
}, [handleDeleteMessage]);

// ...
```

------

`DeleteMessageRequest` のインスタンスを新しく作成して、関連するメッセージ ID をコンストラクタのパラメータに渡し、リクエストを用意します。用意したリクエストを受け入れるために `deleteMessage` を呼び出します。

------
#### [ TypeScript ]

```
// App.tsx

// ...

const handleDeleteMessage = async (id: string) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------
#### [ JavaScript ]

```
// App.jsx

// ...

const handleDeleteMessage = async (id) => {
  const request = new DeleteMessageRequest(id);
  await room.deleteMessage(request);
};

// ...
```

------

次に、`messages` の状態を更新し、削除済みメッセージが消去された新しいメッセージリストを反映させます。

`useEffect` フックでは `messageDelete` イベントを監視し、`message` パラメーターと一致する ID を持つメッセージを削除して、`messages` 状態配列を更新します。

注: `messageDelete` イベントは、現在のユーザー、またはチャットルーム内の他のユーザーによってメッセージが削除された場合に発生します。これを、`deleteMessage` リクエストの後ではなくイベントハンドラーで処理することで、メッセージ削除の処理をまとめることができます。

**TypeScript/JavaScript**:

```
// App.tsx / App.jsx

// ...

const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteMessageEvent) => {
  setMessages((prev) => prev.filter((message) => message.id !== deleteMessageEvent.id));
});

return () => {
  // ...

  unsubscribeOnMessageDeleted();
};

// ...
```

この段階で、チャットアプリのチャットルームからユーザーを削除することが可能になりました。

## 次のステップ
<a name="chat-react-messages-events-next-steps"></a>

実験として、他のユーザーの接続切断など追加のアクションも、ルームに実装してみてください。