エンジニアを目指す日常ブログ

日々勉強したことのメモ。独学ですので間違っていたらコメント等で教えてください。

【React+Node.js/socket.io】チャットアプリに入室機能とユーザ名表示機能を追加する

はじめに

前回、ReactとNode.js Expressを使って、チャットアプリの超基礎となる部分を作成した。

tomiko0404.hatenablog.com

今回は、機能を追加し、さらにチャットアプリに近づける。

youtu.be

追加する機能

発言者名と発言時刻を表示する機能

発言者名と発言の時刻を取得して表示する機能。 発言者名は、入室機能によってブラウザごとに設定する。

発言者名、発言時刻表示機能イメージ
発言者名、発言時刻表示機能

入室機能(簡易)

チャット画面に入る前に、ユーザ名を設定する機能。

入室機能イメージ
入室機能

見た目の改善・コンポーネント

マージンを設定したり、ボタンのデザインを作成。

前提

前回の記事の続きとなります。

tomiko0404.hatenablog.com

最終的なソースコード

ソースコードはこちら。 github.com

修正内容の解説

発言時刻を表示する機能

メッセージ送信機能への変更

前回のApp.tsxのうち、画面サーバ(クライアント側)からWebサーバに送信する内容を増やす。

    // 「送る」ボタンを押したときの処理。サーバにmessageを送信する。
    //  messageContentオブジェクト(中身は名前/メッセージ/発言時刻)
    const onClickSend = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        e.preventDefault();
        const date = new Date();
        const messageContent: MessageContent = {
            name: name,
            message: message,
            postat: date.toLocaleString("ja"),
        };

        socket.emit("chat message", messageContent);
        setMessage("");
    };

時刻を取得するのはJavasciptのDateクラス

const date = new Date();

で実施している。

Webサーバ側の変更

Webサーバ側(index.js)は、オブジェクトを受けとってブロードキャストする動作は変わらないので、特に変更なし。

メッセージ受信、表示機能への変更

あとは画面サーバ側で、ブロードキャストされた値をそれぞれ表示してあげるだけで良い。

    useEffect(() => {
        socket.on("chat", (messageContent) => {
            setMessageList((messageList) => [...messageList, messageContent]);
        });
    }, []);

の部分でサーバから送信されたmessageContentに必要な値が格納されている。

入室機能(簡易)

入室画面とメインチャット画面を作成する

これまで作成してきたメインのチャット画面に加え、入室画面を作成する。

これまで

  • メインチャット画面:/ (例えば開発環境ではhttp:localhost:8000

変更後

  • ログイン画面:/(例えば開発環境ではhttp:localhost:8000
  • メインチャット画面 /main (例えば開発環境ではhttp:localhost:8000/main
ルーティング

ルーティングはReact-router-domを利用した。

tomiko0404.hatenablog.com tomiko0404.hatenablog.com

App.tsx

import "./App.css";

import { ChakraProvider } from "@chakra-ui/react";
import { BrowserRouter } from "react-router-dom";
import { Router } from "./router/Router";

function App() {
    return (
        <ChakraProvider>
            <>
                <BrowserRouter>
                    <Router />
                </BrowserRouter>
            </>
        </ChakraProvider>
    );
}

export default App;

/router/Router.tsx

import { Route, Switch } from "react-router";
import { LoginPage } from "../components/pages/LoginPage";
import { MainChat } from "../components/pages/MainChat";
import { Page404 } from "../components/pages/Page404";


export const Router = () => {
    return (
        <Switch>
            <Route exact path="/" render={() => <LoginPage />} />
            <Route exact path="/main" render={() => <MainChat />} />
        </Switch>
    );
};

入室画面は/pages/LoginPage.tsxに、メイン画面は/pages/MainChat.tsxに切り出す。

入室画面

入室画面では、stateとしてnameを利用する。

<input>に入力されているvaluenameに常にnameに設定する。「入室」ボタンがクリックされたら、onClickJoin関数により/mainのURLに遷移する。その際、stateとして{name: name}が渡される。

tomiko0404.hatenablog.com

/pages/LoginPage.tsx

import { useState } from "react";
import { useHistory } from "react-router";
import { NameInputArea } from "../organisms/NameInputArea";

export const LoginPage = () => {
    // 名前
    const [name, setName] = useState("");
    const history = useHistory();

    // 名前エリアの文字が変更されたときの処理
    const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
        setName(e.target.value);
    };

    // 名前が登録されたときの処理
    const onClickJoin = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        history.push("/main", { name: name });
    };

    return (
        <>
            <NameInputArea onChange={onChangeName} onClick={onClickJoin} value={name} />
        </>
    );
};

/components/organisms/NameInputArea.tsx

import { VFC } from "react";
import { InputArea } from "../molecules/InputArea";

type Props = {
    value: string; // インプットに表示されているテキスト
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
};
export const NameInputArea: VFC<Props> = (props) => {
    const { value, onChange, onClick } = props;
    return (
        <InputArea
            value={value}
            onChange={onChange}
            onClick={onClick}
            placeholder="名前を入力してください"
            buttonText="入室"
        />
    );
};

/components/molecules/InputArea.tsx

import { Input } from "@chakra-ui/input";
import { Flex } from "@chakra-ui/layout";
import { VFC } from "react";
import { PrimaryButton } from "../button/PrimaryButton";

type Props = {
    value: string;
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    placeholder?: string;
    onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
    buttonText: string;
};

export const InputArea: VFC<Props> = (props) => {
    const { value, onChange, placeholder, onClick, buttonText } = props;
    return (
        <Flex align="center" justifyContent="center">
            <Input
                placeholder={placeholder}
                value={value}
                onChange={onChange}
            />
            <PrimaryButton onClick={onClick} disabled={!value}>
                {buttonText}
            </PrimaryButton>
        </Flex>
    );
};
メインチャット画面

前回のApp.tsx/pages/MainChat.tsxに切り出す。

また、入室画面から遷移してきたときのstateオブジェクトを受け取る必要があるので、以下を追加する。

App.tsx

import { useLocation } from "react-router";

type LoginState = {
    name: string;
};

export const MainChat = () => {
    const { state } = useLocation<LoginState>();
    const { name } = state;
    .... 省略 ...
}

見た目の改善

ChakraUIの機能を使って諸々の見た目を改善。 また、Atomicデザインを意識してある程度コンポーネント化した。

ChakraUIの利用にはコツがいるので、以下サイト

Chakra UIの歩き方 & Tips集

等を参考にした。

テキスト入力&送信部を中央添えにする部分は苦労したが、justifyContentを使いこなす必要がありそう。

        <Flex align="center" justifyContent="center">
            <Input />
            <PrimaryButton></PrimaryButton>
        </Flex>

おわりに

少しチャットらしいアプリにすることができた。 誰でも入室できてしまうので、socket.io の room機能など使いこなす必要がありそう。