✌️

Reactを使用した簡易的なアカウント機能の実装

2023/11/06に公開

この記事は何??🧐

この記事では、ReactとAPIを連携させて簡易的なアカウント機能を実装する手順を詳しく解説します。ユーザー登録、ログイン、ユーザー情報の取得・更新、アカウントの削除などの機能を取り上げ、基本的なアカウント認証システムを構築する方法について学びます。このコードを実装することによりあなたのコードは生き生きと踊り出すでしょう!

今回使用するもの

今回のプロジェクトでは、以前私が作成したコードをベースに使用します。そのため、GitHubから該当のリポジトリをクローンしていただけると幸いです。🙏✨

https://github.com/Iccyan21/AccountApi

1.はじめに

最初にお伝えしますが、この記事は分かりにくい部分もあるかもしれませんが、なるべくわかりやすく書くように努力します🤓

ここに至るまでの経緯を簡単に共有させてください。プログラミング言語や技術に関わらず、エラーに関する情報は豊富ですよね。しかし、実際にどのように実装を進めていくか、その手順を解説したり、具体的な「レシピ」を提供する情報は案外少ないと私は感じています。

私もまだ学びの途中ですが、この記事を通じてみなさんと一緒に成長していければと思います。最善を尽くしてサポートすることをお約束します!

先にこちらがこの記事で使用したソースコードですのでめんどくさかったらここからcloneしてください😎

https://github.com/Iccyan21/account-react

プロジェクトのセットアップ

ReactやTypescriptなどの環境は、installされているものとして話を進めていきます
まずはReact(Typescriptベースでの)のプロジェクトを作成してみましょう!
Macだとターミナルから下記のコマンドを実行しましょう

npx create-react-app account-react --template typescript

これでaccount-reactという名のプロジェクトファイルが作成されます。もしプロジェクトファイル名を変更する場合は、適宜指定してください

一度異常がなく作成されているか確認するためターミナルからサーバーが立ち上がるか下記のコマンドを入力し見てみましょう!

cd account-react
npm run start

cd でプロジェクトディレクトリに移動しnpm run start を実行します。すると

Compiled successfully!
You can now view account-react in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.20.10.2:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully
Files successfully emitted, waiting for typecheck results...
Issues checking in progress...
No issues found.

このような文字が表記され

このような画面に遷移できたら大丈夫です!

そして account-react 内でsrcのディレクトリにaccountsディレクトリを作成するためこのコマンドを実行し開発に移っていきましょう。

cd src
mkdir accounts

そして接続や遷移に使うパッケージをaccount-react内でinstallしましょう。

npm install axios      
npm install react-router-dom  
npm install --save-dev @types/react-router-dom

それではVscodeでも何でもいいのでエデイタからプロジェクトファイルを開き、開発に移っていきましょう!!

注意!!

これはReactだけではなく下記の以前私が作成したAPI処理を使いますのでこちらの方もサーバーを立ち上げておいてください!!じゃないと繋がりません!!

https://github.com/Iccyan21/AccountApi

これの解説

https://zenn.dev/iccyan/articles/66e0245c854137

あとデザインはめんどくさいのでやらないです😘

動けばいいので、もしデザインを適用したいなら自分でやるかchatGTPに頼んでやってもらってください❤️

簡易的なアカウント機能の実装

それでは実際に実装に移っていきます。
まず現在のプロジェクトの中身がこのような感じになっています。今回は先ほど作成したaccountsの場所で開発をしていきます。

新規登録画面

今から本格的にコードを書いてみましょう、accountsディレクトリからsignup.tsxを作成します。
このようなコードを書きます。

import React, { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

export const SignUp: React.FC = () => {
  const [user_id, setUser_Id] = useState('');
  const [nickname, setNickname] = useState('');
  const [password, setPassword] = useState('');
  const [password_confirmation, setPasswordConfirmation] = useState('');

  const navigate = useNavigate();

  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // パスワードの一致確認
    if (password !== password_confirmation) {
        console.error("パスワードが一致しません");
        return;
    }

    try {
      // バックエンドにPOSTリクエストを送る(先ほどgit cloneしてい、サーバーを立ち上げたもののURL)
      const response = await axios.post('http://127.0.0.1:8000/accounts/signup/', {
        user_id: user_id,
        nickname: nickname,
        password: password,
        password_confirmation: password_confirmation
        
      });
      alert('アカウントが作成されました');
      navigate("/login");
    } catch (error) {
      setError('アカウントを作成できませんでした...');
    }
  };

  return (
    <div className='Register'>
            <h1>アカウント新規登録画面</h1>
            <form onSubmit={handleSubmit} className="Form">
            <input
                type="text"
                placeholder="ユーザーID"
                value={user_id}
                onChange={(e) => setUser_Id(e.target.value)}
            />
            <input
                type="text"
                placeholder="ニックネーム"
                value={nickname}
                onChange={(e) => setNickname(e.target.value)}
            />
            <input
                type="password"
                placeholder="パスワード"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
            />
            <input
                type="password"
                placeholder="確認パスワード"
                value={password_confirmation}
                onChange={(e) => setPasswordConfirmation(e.target.value)}
            />
            <button type="submit">登録</button>
        </form>
    </div>
        
  );
};

export default SignUp;

簡単に解説していきます。

useStateフック

まず最初にuseState フックを使用して、各フォームフィールド(user_id, nickname, password, password_confirmation)とエラーメッセージ(error)の状態を管理しています。
初期値はすべて空の文字列ですが、errorはnullです。

フォーム送信処理 (handleSubmit 関数)

フォームを送信する際に呼ばれる非同期関数です。

デフォルトのフォーム送信を阻止するために、e.preventDefault()を呼んでいます。

パスワードが一致するかどうかを検証し、一致しない場合はエラーをコンソールに出力して処理を終了します。
axios.postを使用して、サーバーに登録データを送信しています。

エラーハンドリング

エラーが発生した場合には、setErrorを使用してエラーメッセージを状態に設定します。
このエラーメッセージは、コード中には表示されていませんが、必要であればユーザーにフィードバックを提供するためにUIに表示できます。

FormUI:

input タグを使用して、ユーザーID、ニックネーム、パスワード、確認パスワードの入力フィールドを提供しています。
各 input 要素には value と onChange ハンドラーが設定されており、これにより入力の変更を状態に反映させることができます。
placeholder 属性を使用して各フィールドの目的を示しています。

このコードを書いたらApp.tsxを下記のように書き換えます。

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import  SignUp  from './accounts/signup';

function App() {
  return (
    <div className="App">
       <BrowserRouter basename='/'>
          <Routes>
            <Route path="/signup" element={<SignUp />}/>
          </Routes>
        </BrowserRouter>
    </div>
  );
}

export default App;

このように書き換えサーバーを立ち上げ、しっかりと接続できているか確認しましょう。

npm run start

サーバーを立ち上げたらhttp://localhost:3000/signup ここのurlにいくと

このような画面が表示されますのでFormを全て入力して送信しましょう。

成功すれば

このように成功したという表記が出ます。

バックエンドにもこのような表示が出力されます。
これで新規登録画面は終了です。

ログイン処理の実装

それではaccountのところにlogin.tsxを作成し下記のコードを書いていきましょう。

import React, { useState, FormEvent } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

export const Login = () => {
  const [user_id, setUser_Id] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();
  const [error, setError] = useState('');

  const handleSubmit = (event: FormEvent) => {
    event.preventDefault();

     // バックエンドにPOSTリクエストを送る(先ほどgit cloneしてい、サーバーを立ち上げたもののURL)
    axios.post("http://127.0.0.1:8000/accounts/login/", {
      user_id: user_id,
      password: password,
    })
    .then((response) => {
     // ここでユーザー情報をローカルストレージに保存します
      localStorage.setItem('user_id', response.data.user_id);
      alert('ログイン成功!');
      navigate("/");
    })
    .catch((error) => {
      console.error(error);
      setError('ログインに失敗しました。');
    });
  };

  return (
    <div className='Login'>
        <h1>ログイン</h1>
        <form onSubmit={handleSubmit}>
        <input
            type="text"
            value={user_id}
            onChange={(e) => setUser_Id(e.target.value)}
            placeholder="ユーザーID"
            required
        />
        <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="パスワード"
            required
        />
        {error && <div style={{ color: 'red' }}>{error}</div>}
        <button type="submit">ログイン</button>
        </form>
    </div>
    
  );
};

export default Login;

解説

State変数の定義

useState フックを使用して、user_id と password のための状態変数とセッター関数を宣言しています。これらは、フォームに入力されたデータを追跡します。また、エラーメッセージを表示するための error 状態変数も宣言しています。

ナビゲーションのセットアップ

useNavigate フックは、ログインに成功した後のリダイレクトに使用されます。

イベントハンドラの定義

handleSubmit 関数は、フォームが送信されたときに呼び出されます。この関数は、デフォルトのフォーム送信イベントを防ぎ、axios を使ってサーバーに user_id と password をPOSTリクエストとして送信します。

リクエストの送信とレスポンスの処理

axios.post メソッドを使用してサーバーにリクエストを送信し、成功した場合はユーザーをホームページにリダイレクトします。

フォームのレンダリング:

ユーザーIDとパスワードの入力フィールドを含むフォームをレンダリングします。各入力フィールドは、対応する状態変数にバインドされており、値が変更されるとその状態が更新されます。また、required 属性があるので、これらのフィールドが空のままではフォームを送信できません。

このコードを書いたらApp.tsxを下記のように書き換えます。

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import  SignUp  from './accounts/signup';
import  Login  from './accounts/login';

function App() {
  return (
    <div className="App">
       <BrowserRouter basename='/'>
          <Routes>
            <Route path="/signup" element={<SignUp />}/>
            <Route path="/login" element={<Login />}/>
          </Routes>
        </BrowserRouter>
    </div>
  );
}

export default App;

このように書き換えサーバーを立ち上げ、しっかりと接続できているか確認しましょう。

npm run start

サーバーを立ち上げたらhttp://localhost:3000/login
ここのurlにいくと

この画面が表示されると思うので先ほど登録したアカウントのユーザーIDとパスワードを入力しましょう!

ログインが成功するとこのような画面が出て何もない
http://localhost:3000/profile
この画面に遷移してるのでこれができていればログイン処理の実装は終わりです!

ユーザー情報取得

それではaccountのところにprofile.tsxを作成し下記のコードを書いていきましょう。

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

// ユーザー情報を表すインターフェイスを定義
interface User {
  user_id: string;
  nickname: string;
  comment: string;
}

const Profile: React.FC = () => {
  const [user, setUser] = useState<User | null>(null);
  const [error, setError] = useState<string>('');
  const navigate = useNavigate();

  useEffect(() => {
    const fetchUserProfile = async () => {
      const user_id = localStorage.getItem('user_id');
      if (!user_id) {
        navigate('/login');
        return;
      }
  
      try {
        const response = await axios.get(`http://localhost:8000/accounts/users/${user_id}/`);
        setUser(response.data.user);
      } catch (err) {
        // ここでエラーの型チェックを行います
        if (axios.isAxiosError(err)) {
          const errorMsg = err.response?.data?.message || 'ユーザー情報の取得に失敗しました。';
          setError(errorMsg);
        } else {
          setError('An unexpected error occurred.');
        }
        console.error('エラー発生', err);
      }
    };
  
    fetchUserProfile();
  }, [navigate]);
  

  return (
    <div className="user-profile">
      <h1>ユーザープロフィール</h1>
      {user && (
        <div>
          <p>ユーザーID: {user.user_id}</p>
          <p>ニックネーム: {user.nickname}</p>
          <p>コメント: {user.comment}</p>
        </div>
      )}
    </div>
  );
};

export default Profile;

解説

インポート部分

useState と useEffect はReactのフックで、状態管理と副作用(サイドエフェクト)の実行に使います。
axios はPromiseベースのHTTPクライアントで、非同期のAPIリクエストを行うために使用されます。
useNavigate はReact Routerのフックで、プログラム的にナビゲーション(ページ遷移)を制御するために使用されます。

User インターフェイス

User インターフェイスは、ユーザー情報を表すためのオブジェクトの型を定義します。user_id、nickname、comment の3つの文字列プロパティがあります。

Profile コンポーネント
Profile コンポーネントは以下の機能を持っています。

状態(State)

user: ユーザー情報を保存します。初期状態は null です。
error: エラーメッセージを保存します。初期状態は空の文字列です。
副作用(Effect):
useEffect フックはコンポーネントがマウントされた後に実行されます。ここで行われることは:

localStorage から user_id を取得します。
user_id が存在しない場合、ログインページへリダイレクトします。
user_id が存在する場合、axios.get を用いてユーザー情報を取得するAPIリクエストを非同期で行います。
リクエストが成功した場合、取得したユーザー情報を user 状態にセットします。
エラーが発生した場合、エラーメッセージを error 状態にセットし、コンソールにエラーを出力します。

レンダリング

コンポーネントは、ユーザー情報が存在する場合にそれを表示します。user 状態が null でない場合(つまりユーザー情報が取得できた場合)、ユーザーID、ニックネーム、コメントが表示されます。

このコードを書いたらApp.tsxを下記のように書き換えます。

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import SignUp  from './accounts/signup';
import Login  from './accounts/login';
import Profile from "./accounts/profile";


function App() {
  return (
    <div className="App">
       <BrowserRouter basename='/'>
          <Routes>
            <Route path="/signup" element={<SignUp />}/>
            <Route path="/login" element={<Login />}/>
            <Route path="/profile" element={<Profile />}/>
          </Routes>
        </BrowserRouter>
    </div>
  );
}

export default App;

このように書き換えサーバーを立ち上げ、しっかりと接続できているか確認しましょう。

npm run start

サーバーを立ち上げたら、http://localhost:3000/profile ここのurlに飛ぶと

このように先ほどログインしたユーザーの情報が表示されます。
この画面に遷移してるのでこれができていればユーザー情報取得処理の実装は終わりです!

ユーザー情報編集

それではaccountのところにupdate.tsxを作成し下記のコードを書いていきましょう。

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';

// ユーザー情報を表すインターフェイスを定義
interface User {
  user_id: string;
  nickname: string;
  comment: string;
}

interface UserUpdateData {
  nickname?: string;
  comment?: string;
}

const UserUpdate = () => {
  const { user_id } = useParams();
  const [user, setUser] = useState<User | null>(null); 
  const [nickname, setNickname] = useState('');
  const [comment, setComment] = useState('');
  const [error, setError] = useState<string | null>(null);

  // ユーザー情報をフェッチする関数
  const fetchUser = async () => {
    try {
      const response = await axios.get(`http://localhost:8000/accounts/users/${user_id}/`);
      setUser(response.data.user);
    } catch (err) {
      // ここでエラーの型チェックを行います
      if (axios.isAxiosError(err)) {
        const errorMsg = err.response?.data?.message || 'ユーザー情報の取得に失敗しました。';
        setError(errorMsg);
      } else {
        setError('An unexpected error occurred.');
      }
      console.error('エラー発生', err);
    }
  };

  useEffect(() => {
    if (user_id) fetchUser(); // コンポーネントのマウント時にユーザー情報をフェッチ
  }, [user_id]);

  const handleUpdate = async () => {
    setError(null); // エラーをリセット

    const updateData: UserUpdateData = {
      nickname: nickname,
      comment: comment,
    };

    try {
      const response = await axios.patch(`http://localhost:8000/accounts/users/${user_id}/update/`, updateData);
      alert('ユーザー情報が更新されました。');
    } catch (error) {
      if (axios.isAxiosError(error) && error.response) {
        setError(error.response.data.message || '更新中にエラーが発生しました。');
      } else {
        setError('予期しないエラーが発生しました。');
      }
    }
  };

  return (
    <div>
      {user ? ( // user情報があるか確認します。
        <>
          <p>ユーザーID: {user.user_id}</p> {/* ここでuserのuser_idを表示 */}
          <p>ニックネーム: {user.nickname}</p> {/* ここでuserのnicknameを表示 */}
          <p>コメント: {user.comment}</p> {/* ここでuserのcommentを表示 */}
        </>
      ) : (
        <p>ユーザー情報を読み込んでいます...</p>
      )}

      <input
        type="text"
        value={nickname}
        onChange={(e) => setNickname(e.target.value)}
        placeholder="ニックネーム"
      />
      <textarea
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        placeholder="コメント"
      />
      {error && <p className="error">{error}</p>}
      <button onClick={handleUpdate}>更新</button>
    </div>
  );
};

export default UserUpdate;

解説

インポート部分

Reactフック(useState, useEffect): 状態管理とライフサイクルイベントを扱うための機能。
axios: HTTPリクエストを簡単に行うためのライブラリ。
useParams: 現在のURLの動的な部分をフックで取得するためのReact Routerの機能。
User インターフェイス
Userインターフェイスは、ユーザーのデータ構造を定義します。これにより、ユーザーデータを扱う際の型安全性を高めることができます。

Profile コンポーネントの機能

状態(State)
user: APIから取得したユーザー情報を保持します。初期状態はnullです。
error: エラーメッセージを保持します。初期状態は空文字列です。
副作用(Effect)

useEffect:

コンポーネントがマウントされた際に、localStorageからuser_idを取得し、APIリクエストを実行してユーザー情報をフェッチします。

APIリクエスト

ユーザー情報の取得: axios.getを使用してユーザー情報を取得します。成功時にはユーザー情報をuserステートにセットし、失敗時にはエラーメッセージをerrorステートにセットします。

レンダリング

ユーザー情報の表示: userステートがnullでなければ、ユーザーのID、ニックネーム、コメントを表示します。
エラーメッセージの表示: errorステートが空文字列でなければ、エラーメッセージを表示します。

このコードを書いたらApp.tsxを下記のように書き換えます。

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import SignUp  from './accounts/signup';
import Login  from './accounts/login';
import Profile from "./accounts/profile";
import UserUpdate from './accounts/update';


function App() {
  return (
    <div className="App">
       <BrowserRouter basename='/'>
          <Routes>
            <Route path="/signup" element={<SignUp />}/>
            <Route path="/login" element={<Login />}/>
            <Route path="/profile" element={<Profile />}/>
            <Route path="/update/:user_id" element={<UserUpdate />} />
          </Routes>
        </BrowserRouter>
    </div>
  );
}

export default App;

このように書き換えサーバーを立ち上げ、しっかりと接続できているか確認しましょう。

npm run start

サーバーを立ち上げたら、http://localhost:3000/upddate/:user_id/ (ここのuser_idはさっき作成したアカウントのuser_idのこと)ここのurlに飛ぶと

デザインは壊滅的ですがこのような画面が表示されると思います。

このようにFormに記入し更新ボタンを押すと

こちらのアクションが表示されもう一度読み込むと

このように更新されていると思います。これでユーザー情報編集処理は終了です

アカウント削除処理

それではaccountのところにdelete.tsxを作成し下記のコードを書いていきましょう。

import React, { useState } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';

interface DeleteProps {
  user_id: string; // `user_id`の型を指定
}

const Delete = () => {
  const [error, setError] = useState<string | null>(null);
  const { user_id } = useParams(); 

  const userId = user_id as string;


  const handleDelete = async () => {
    setError(null); // Reset errors

    try {
      await axios.post(`http://localhost:8000/accounts/delete/${user_id}/`); // `user_id`を正しく使用
      alert('アカウントが正常に削除されました。');
      // Redirect or logout user
    } catch (error: any) { // TypeScriptの場合、errorの型を指定する
      if (axios.isAxiosError(error) && error.response) {
        // Handle server response error
        setError('アカウントの削除に失敗しました: ' + error.response.data.message);
      } else {
        // Handle other errors
        setError('アカウントの削除中に問題が発生しました。');
      }
    }
  };

  return (
    <div>
       <h1>アカウント削除</h1>
       <p>ユーザーID: {userId}</p>
       <p>アカウントを削除しますか?</p>
      <button onClick={handleDelete}>アカウントを削除</button>
      {error && <p className="error">{error}</p>}
    </div>
  );
};

export default Delete;

解説

インポート部分

このコンポーネントでは、ReactのフックであるuseStateとuseEffectを使用しています。これらのフックは状態管理とコンポーネントのライフサイクル内での副作用(サイドエフェクト)を扱うために用いられます。また、HTTPクライアントライブラリであるaxiosをインポートしており、これによりPromiseベースでの非同期APIリクエストが可能になります。useNavigateはReact Routerのフックで、コンポーネントからナビゲーション(ページ遷移)をプログラム的に制御するために使われます。

User インターフェース

User インターフェースでは、ユーザー情報を表すオブジェクトの形を定義しています。このインターフェースはuser_id、nickname、commentという3つの文字列プロパティを持っています。

Profile コンポーネント

Profile コンポーネントは、以下の機能を提供します:

状態管理(State)
user: ユーザー情報を保持するための状態です。初期値はnullで、これはユーザー情報がまだフェッチされていないことを意味します。
error: エラーメッセージを格納するための状態です。初期値は空文字列で、エラーが発生していない状態を示します。
副作用(Effects)
useEffectフックはコンポーネントのマウント後に実行される副作用を定義します。具体的には:

localStorageからuser_idを取得します。このIDはユーザーのセッション情報として保存されることが一般的です。
取得したuser_idが存在しない場合は、ログインページへリダイレクトする処理を行います。これはユーザーが認証されていない場合のハンドリングです。
user_idが存在する場合は、axios.getを使用してユーザー情報を取得するための非同期APIリクエストを行います。
APIリクエストが成功した場合は、取得したデータをuser状態にセットします。
APIリクエストでエラーが発生した場合は、そのエラーメッセージをerror状態にセットし、詳細をコンソールに出力します。

レンダリング

Profileコンポーネントは、取得したユーザー情報を画面に表示するためのUIをレンダリングします。user状態がnullでなければ(ユーザー情報が正常にフェッチされた場合)、そのユーザーのID、ニックネーム、コメントを表示します。エラーが発生している場合は、そのエラーメッセージをユーザーに知らせるために表示します。

このコードを書いたらApp.tsxを下記のように書き換えます。

import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import SignUp  from './accounts/signup';
import Login  from './accounts/login';
import Profile from "./accounts/profile";
import UserUpdate from './accounts/update';
import Delete from './accounts/delete';

function App() {
  return (
    <div className="App">
       <BrowserRouter basename='/'>
          <Routes>
            <Route path="/signup" element={<SignUp />}/>
            <Route path="/login" element={<Login />}/>
            <Route path="/profile" element={<Profile />}/>
            <Route path="/update/:user_id" element={<UserUpdate />} />
            <Route path="/delete/:user_id" element={<Delete />} />
          </Routes>
        </BrowserRouter>
    </div>
  );
}

export default App;

このように書き換えサーバーを立ち上げ、しっかりと接続できているか確認しましょう。

npm run start

サーバーを立ち上げたら、http://localhost:3000/delete/:user_id/ (ここのuser_idはさっき作成したアカウントのuser_idのこと)ここのurlに飛ぶと

このような画面に遷移すると思うのでアカウント削除ボタンを押しましょう!

このようなアクションが出れば成功です。これでユーザー削除処理は終了です。
ここまできた自分を褒めてあげてください。

アカウント認証機能の実装に対する感想と振り返り

これでReactを使用したアカウント認証機能の実装は終了となります

特に後半は睡魔に襲われながら書いていたので確認しましたが間違ってるかもしれません😱

もしわからないところがあれば気軽に質問してください!!

下記にGitのコードを載せておくので、参考程度に拝見ください!

https://github.com/Iccyan21/account-react

こちらがtwitterアカウントなのでよければフォローお願いします!!

https://twitter.com/GIANT_KILLING_0

最後まで拝見いただき、ありがとうございました!!

Discussion