【React, Material UI】SIerエンジニアが1週間でポートフォリオサイトを完成させるまで

私は現在、SIerから自社サービス企業への転職を目指しています。
その準備のひとつとして自身のポートフォリオサイトを作成したので、完成までの流れをまとめました。

Reactで簡単な開発を行う流れやUIライブラリのMaterialUIを使って短時間でサイトを作成したい方の参考になれば嬉しいです！

# アジェンダ

- [もう少し自己紹介](#もう少し自己紹介)
- [使用技術](#使用技術)
- [アーキテクチャ構成](#アーキテクチャ構成)
- [フロントエンド](#フロントエンド)
- [バックエンド](#バックエンド)
- [デプロイ](#デプロイ)
- [ハマったこと](#ハマったこと)
- [さいごに](#さいごに)

# もう少し自己紹介

私は現在メーカー系SIer3年目で、主にサーバーサイドエンジニアとして働いておりフロントエンドの開発歴は今まで合わせて4ヶ月程度です。
業務で初めてReactの開発を行うこととなったときはそもそもフロントエンド自体UdemyでHTMLを少し勉強したことある程度で焦りましたが、簡単な画面だったこともありReactを学びながらHTML, CSSの知識も強化することができ良い経験でした。

もっと個人的なことは今回作成したサイトに書いています！

https://suidev.github.io/portfolio/

# 使用技術

今回使用した主な技術をまとめました。

* フロントエンド 
  * React (create-react-app, JavaScript)
  * Material UI (UIライブラリ)
  * styled-components （CSSライブラリ）
  * react-responsive　/ styled-media-query (レスポンシブ対応)

* クラウド
  * AWS (API Gateway, Lambda(Python)) (Web APIの作成)

* デプロイ
  * GitHub Pages

* その他
  * Slack incoming webhook (Slackへのチャットの送信)

# アーキテクチャ構成

基本的にはReactをビルドしたファイルをGitHub Pagesで公開しているという流れです。
それに加えて、今回は問い合わせフォームを作成し入力された内容をSlackに送る機能を考え、そのためにAWSでサーバーレスAPIを作成しました。

# フロントエンド

今回はできるだけ早く完成させることを第一にしていたためNext.jsやTypeScriptは使用せず、webpackの設定なども必要ないcreate-react-appでプロジェクトを作成しました。

https://ja.reactjs.org/docs/create-a-new-react-app.html

コンポーネントの設計としてはAtomic Designを参考にしましたが、まだ理解は浅く独自の構成になってしまった部分もあると思います。
例えば、表示されない機能だけのコンポーネントをutilsというディレクトリにまとめたりしています。 

CSS関連では、大まかな画面デザインはMaterial UIのBoxやGridコンポーネントを使用して細かい部分はstyled-componentsにて行いました。
また、レスポンシブデザインとしたかったため、react-responsiveにて画面幅ごとに表示コンポーネントの切り替えをし、styled-media-queryにてstyled-componentsのCSSの切り替えをしました。

https://mui.com/components/box/

https://mui.com/components/grid/

そしてWeb APIとの連携にはaxiosを使用しました。

# バックエンド

アーキテクチャ構成にも書きましたが、今回はSlackにお問い合わせフォームの内容を送るAPIのみ用意する必要がありました。
そのため、まずはSlack APIからincoming webhookのURLを生成しました。
生成したURLの扱いについて、送信専用なのでセキュリティ的にデータ流出のリスクはありませんが、念のため荒らしなどの対策も含めAWSのAPI GatewayとLambdaを使いフロントエンドから見えないようにしました。
同時にAPI自体のリクエスト数なども簡単に設定できて本当に便利ですね、、

# デプロイ

GitHub Pagesでのデプロイはgh-pagesというライブラリが便利そうだったので使用しました。
GitHubリポジトリの設定から一連の流れは以下の記事を参考にさせていただきました！

https://web-begginer-log.com/create-react-app-gh-pages/

# ハマったこと

本題とはそれますが今回の開発で一番ハマったことを1つ紹介します。

内容は、Material UIのモーダルの中にテキストの入力欄を配置したとき、入力値をuseStateを使って保持する実装をすると、入力欄のフォーカスが1文字入力するとすぐ外れてしまうという現象です。

今回は以下のようなお問い合わせフォームを作成しています。
![スクリーンショット 2022-03-03 22.21.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626807/a79afb38-d612-9f03-a80c-b26488e1350e.png)
このフォームで会社名をクリックして"a"と入力すると、すぐに会社名欄からフォーカスが外れて入力ができなくなってしまいました。
外れたフォーカスがどこにいったのか調べるとモーダル自体にいっているようで、しばらく悩みuseStateを使ったことで再レンダリングが走っていることが原因でないかと考えました。

そのためuseStateを使わない方法を考え、以下のように送信ボタンを押したのみ各入力欄の値を取得し送信する方式として解決しました。
入力中に値のバリデーションなどを行いたい場合はこの方法ではいけませんが、今回は簡易的なフォームであることからこれで良いことにしました。

* 入力ができなくなってしまうコード
```
const Contact = props => {
  // 会社名の入力値の保持
  const [company, setCompany] = useState("");
  const handleSetCompany = (e) => {
    setCompany(e.target.value);
  }

  // 氏名の入力値の保持
  const [name, setName] = useState("");
  const handleSetName = (e) => {
    setName(e.target.value);
  }

  // メールアドレスの入力値の保持
  const [mail, setMail] = useState("");
  const handleSetMail = (e) => {
    setMail(e.target.value);
  }

  // 問い合わせ内容の入力値の保持
  const [content, setContent] = useState("");
  const handleSetContent = (e) => {
    setContent(e.target.value);
  }

  const Contents = () => (
    <>
      ...
        <TextField
          id="company"
          label="会社名"
          variant="standard"
          required
          value={company}
          onChange={handleSetCompany}
        />
        <TextField
          id="name"
          label="お名前"
          variant="standard"
          required
          value={name}
          onChange={handleSetName}
        />
        <TextField
          id="mail"
          label="メールアドレス"
          variant="standard"
          required
          value={mail}
          onChange={handleSetMail}
        />
        <TextField
          id="content"
          label="お問い合わせ内容"
          variant="standard"
          required
          value={content}
          onChange={handleSetContent}
          multiline
          rows={8}
        />
      ...
    </>
  );
```

* 修正後のコード
```
  // 送信ボタンのonClickイベントメソッド
  const handleSendMessage = () => {
    const company = document.getElementById("company").value;
    const name = document.getElementById("name").value;
    const mail = document.getElementById("mail").value;
    const content = document.getElementById("content").value;

    // 送信処理
　　　　　　　...
  }

  const Contents = () => (
    <>
      ...
        <TextField
          id="company"
          label="会社名"
          variant="standard"
          required
        />
        <TextField
          id="name"
          label="お名前"
          variant="standard"
          required
        />
        <TextField
          id="mail"
          label="メールアドレス"
          variant="standard"
          required
        />
        <TextField
          id="content"
          label="お問い合わせ内容"
          variant="standard"
          required
          multiline
          rows={8}
        />
      ...
    </>
  );
```

# さいごに

↓今回作成したコードのリポジトリです。

https://github.com/SuiDev/portfolio

コードについての内容はほぼ書いていないので、気になる内容などありましたらコメントやTwitterからご連絡いただければ知識の範囲で答えさせていただきます！
また、少しでも興味持っていただいた方や同じような状況の方からのご連絡などもお待ちしております！