20210412のReactに関する記事は14件です。

Docker/React初心者が半日で構築するReact環境(1週間だけ頑張る Day3of9)

はじめに 1週間N(E)ETになった社会人の学習記録です。 前回作ったFastAPIに対して、CRUDする予定だったのですが、まずは、Reactの環境をDocker上に構築しました。 参考ページ Google先生の教えの中から分かりやすそうな記事を参考にする。 1. Dockerfile 作成 参考ページはディレクトリ作っているぐらいだったので、不要と判断。 2. docker-compose.yml 作成 できるだけデフォルトに寄せて設定項目を減らした。 docker-compse.yml version: "3" services: # 略 # React web: container_name: "web" image: node:15.14.0-buster-slim ports: - 3000:3000 environment: LANG: ja_JP.UTF-8 TZ: Asia/Tokyo NODE_ENV: deveropment volumes: - type: bind source: /mnt/c/Users/Public/GitHub/prac/docker/python_fastapi/web/app target: /app command: sh -c "cd /app && yarn start" 設定項目 備考 サービス名 参考資料1のTips image nodeのデフォルトはdebian系だったから ports Reactのデフォルトは3000っぽい environment 下表参照 volumes お好み設定 command 環境変数名 設定値 備考 LANG ja_JP.UTF-8 日本語2 TZ Asia/Tokyo 日本時間2 NODE_ENV deveropment Docker使うならproductionでいいかなと思ったが、「To create a production build, use yarn build.」とでるのでdeveropmentにした。2 3 4 3./4./8. appフォルダの作成とReactの起動 参考資料のまま、以下のコマンドをWSLのUbutuで実行。 結構時間がかかるけど、/appフォルダ内にReact本体が作成される。 localhost:3000にアクセスすると、Reactのロゴがぐるぐる回る。 docker-compose build docker-compose run --rm app npx create-react-app app --template typescript docker-compose up 5./6./7. 多分不要 5. appフォルダ内の移し替え Dockerfile内でWORKDIRを設定していないため、PWDは/(root)のまま。 /にて「npx create-react-app app --template typescript」を実行すると、 /app 内にReact本体が展開されたので、本手順は不要だった。 6. .gitignoreの編集 /app/node_modulesはgitなどで別途管理する必要があると思う。 この本体部分が/usr/local以下とかの全体にインストールされるようなら、そういうDockerイメージを公式が出してほしい。 Dockerhubに無い時点で今回の手順を参考にした。 7. portの変更 デフォルトの3000番を使うので変更不要。 所感 鶏と卵問題のせいで、一番最初はbuildとrunとupを別にするのはだるい(まあ、DockerのCLIを立ち上げて中で打つよりは幾分マシだが)。 同じ問題はRailsの構築5 6 でも見て取れるので、仕方ないような気がする。(Railsの開発時はRails createコマンドとかはコンテナ側で実行するのかホスト側で実行するのか気になる。) コンテナの再立ち上げの際に数分待たされるほど遅いような気がする。(production版にすれば多少マシになるのかな?) Reactのロゴが原子核っぽいのとReactorが原子炉ってことを考えると、開発陣に原子核物理の人でもいるのかな。 免責事項(言い訳) 上記の記載は初心者の試行錯誤なので、あてにしないように!!! https://zenn.dev/mochiblock/articles/3af073ff13c6e2 ↩ https://qiita.com/daikideal/items/ac36987dca0fa742e193 ↩ https://qiita.com/hasehiro0828/items/f4275c9a2175864c82e4 ↩ https://qiita.com/habi16/items/497abe1f80228a2b4ff9 ↩ https://qiita.com/kodai_0122/items/795438d738386c2c1966 ↩ https://qiita.com/chisaki0606/items/a4b42af5c4735c94057a ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + Rails のSPAでshowアクションを実装する

これは何? React+Railsで簡単なタスク管理アプリを作っています。 一覧画面からタスク名のリンクをクリックすると、詳細画面が現れます。 実行環境は以下の通りです。 Rails 6.0.3 React 17.0.2 また、今回のディレクトリ構成は以下の通りです。(関係のある箇所だけ表示) . ├── controllers │   └── api │      └── tasks_controller.rb └── javascript    └── pages    ├── Task.jsx    └── Tasks.jsx Rails側 spaではないRailsのアプリとほぼ変わりありません。/api配下に置いたコントローラーにindex(実装については前回の記事をご参照ください)とshowアクションを記載します。 render json: ...でJSON形式でビューを描画します。 app/controllers/api/tasks_controller.rb class Api::TasksController < ApplicationController def index @tasks = current_user.tasks render json: @tasks end def show @task = current_user.tasks.find(params[:id]) render json: @task end end SPA側(一覧表示画面) タスク一覧表示画面(Tasksコンポーネント)に実際に書いたコードがこちらです。 app/javascript/pages/Tasks.jsx import React, {useState} from 'react' import { Link, withRouter } from 'react-router-dom' import axios from 'axios' export const Tasks = withRouter(() => { // ★2解説します const [tasks, setTasks] = useState([]) React.useEffect(async () =>{ const response = await axios.get('/api/tasks'); setTasks(response.data) }, []) return ( <div> <h1>タスク一覧</h1> <table> // 中略 <tbody> {tasks.map(task => ( <tr key={task.id}> <td> <Link to={{ // ★1解説します pathname: "/spa/tasks/" + task.id, state: {id: task.id}, }}>{task.name}</Link> </td> // 中略 </tr> ))} </tbody> </table> </div> ) }) react-router と react-router-dom まず、調べていて迷ったのが、react-routerとreact-router-domがあるっぽいということ。その点は、こちらの記事が非常に丁寧に解説してくれていました。 react-routerとreact-router-domの違い 要するに、react-router-domの方が新しくて使い勝手も良いそうなので、react-router-domを使いましょう、とのことでした。 Link to ポイントの一つ目はこの部分(★1)で、 <Link to={{ pathname: "/spa/tasks/" + task.id, state: {id: task.id}, }}>{task.name}</Link> react-router-domのLinkという関数で、遷移先や遷移先のページに送るデータなどが指定できます。詳しくは公式のAPIドキュメントがわかりやすかったですが、 Link 上記を指定することで、受取手側のコンポーネントではlocatitonで以下のような情報が取得できました。 console.log(location) => {pathname: "/spa/tasks/1", state: {id: 1}, search: "", hash: "", key: "5ox837"} withRouter ただし、上記だけではlocationのstateがundefined になります。調べてみるとwithRouterでラップしてねという回答がたくさん出てきたので、今回も以下のように全体をラップしてみました(★2)。 export const Tasks = withRouter(() => { // 略 }) 公式のドキュメントもみてみると、 withRouter withRouter will pass updated match, location, and history props to the wrapped component whenever it renders. (withRouterは、レンダリングされるたびに、更新されたmatch、location、およびhistoryのpropsをラップされたコンポーネントに渡します。) とのことでした。Functional ComponentでのwithRouterの使い方は、こちらを参考にしました。 Functional ComponentでもReact RouterのwithRouterを使う方法 完成 以上で、実装は終わりになります。 まさかShowアクションでここまで調べることになるとは思わなかった...^^ 本日はDestroyアクションも試してみたのですが、そちらはまた別の機会に記事にしたいです。 ではでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React Hooks】ライフサイクルについて

概要  reactのライフサイクルを扱うフックuseEffectについて学んだことを共有する。 ライフサイクルとは  ライフサイクルとはコンポーネントの処理の流れを表しています。コンポーネントがいきなり画面に表示されるのではなく、constructorをコールして、render()をコールしてなど、手順を踏んでユーザーの画面に情報を描画されます。この処理の流れのことをライフサイクルと呼びます。 3つのライフサイクル  Reactのclassで設定できるライフサイクルは大きく分けて3つの処理があります。 componetDidMount componentDidUpdate componentWillUnmount です。それぞれ処理される順番を図にまとめると以下のようになります。 commponentDidMount 一番始めのレンダーされた直後に一度だけコールされるメソッドです。 イベントリスナーの設定、API通信の記述などをする。 componentDidUpdate コンポーネントが表示された後、ユーザーによる操作(クリック、スクロールなど)によって、再レンダーされた直後にコールされるメソッドです。 ユーザーが操作し、再レンダーするたびにコールされる。 componentWillUnmount 画面遷移などして、コンポーネントが破棄される前にコールされるメソッドです。 イベントリスナーの解除、API通信の切断などに記述する。 useEffectについて  関数コンポーネントでは、フックであるuseEffectを使用して、ライフサイクルを代替的に実現するのですが、まずuseEffectがどういったものなのかを述べます。useEffectは副作用フックと呼ばれ、副作用を実現することができます。データの取得や、DOMの操作などは適切な箇所で操作する必要があります。例えば、DOM操作に関して、レンダーした後ではないと、DOM操作はできません。レンダーする前にDOM操作するとDOMが生成されていないためエラーになります。そのようにライフサイクルにおいて適切な箇所に適切な処理を記述してあげることを副作用と呼びます。  useEffectの使用例を以下に示します。基本の形は、useEffectの第一引数にコールバック関数を指定すると、レンダーされるたびにそのコールバック関数がコールされます。 useEffect.tsx import {useEffect} from 'react'; userEfffect( () => { console.log('レンダーされるたびにコールされる'); ]); また、第二引数に配列を指定することができます。例えば、第二引数にvalueという変数を指定すると、valueの値が変更するたびにコールされます。第二引数を指定しても初回レンダー時は必ずコールされるため注意する必要があります。第二引数の配列の中を空白にすると、初回レンダー時にしかコールされません。 import {useEffect} from 'react'; userEfffect( () => { console.log('第二引数に指定された値が変化するたびにコールされる'); ], [value]); import {useEffect} from 'react'; userEfffect( () => { console.log('初回レンダー時のみコールされる'); ], []); returnにはクリーンアップ関数を指定することができます。クリーンアップ関数とは、コンポーネントが破棄される際にコールされる関数です。主にメモリの開放処理などを行うため、クリーンアップ関数と呼ばれます。注意してほしいのは、再レンダーされるたびに、前回のクリーンアップコンポーネントが実行されます。動きとしては、再レンダー→以前のクリーンアップ関数を実行→今回のuseEffectの第一引数のコールバック関数を実行となります。 import {useEffect} from 'react'; userEfffect( () => { return () => console.log('コンポーネントが破棄されるときにコールされる'); ]); フックによるライフサイクルの実現  Reactのフックでは、useEffectを使用して前章で示したライフサイクルを代替的に実現することができます。 componentDidMount example.tsx import {useEffect} from 'react'; userEfffect( () => { console.log('componentDidMount'); ], []); componentDidUpdate example.tsx import {useEffect} from 'react'; userEfffect( () => { if(/* ユーザーの操作を指定 */'){ console.log('componentDidUpdate'); } ]); componentWillUnmaout example.tsx import {useEffect} from 'react'; userEfffect( () => { return () => { console.log('componentWillUnmount'); } ]); 活用例  componentDidMountとcomponentDidUpdateの処理が同じで、componentWillUnmountも指定するのであれば、以下のように記述することで実現できる。 example.tsx import {useEffect} from 'react'; userEfffect( () => { console.log('componentDidMountとcomponentDidUpdate'); return () => { console.log('componentWillUnmount'); } ]); あとは、以上のことを組み合わせることで自分が実現したい機能を実現すればよいです! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

俺なりNode環境構築、ドラゴンボールを求めて

はじめに 俺の考えた結果 開発環境 環境作っていくよーん 番外編 まとめ 参考文献 俺の考えた結果 野暮用でReactをやりたくなって環境構築を思い立ったところ、いろいろなNodeの環境構築方があることを知った。nodebrewやらanyenv入れてnodenv入れてとか、、、。npmとかyarnとか、、、 私が個人的にpyenv + pipenvでPythonの環境を作っていることもあり、できるだけ似たようなもので整えたかった。 個人的に納得のいく環境構築ができたので、備忘録として残しておく。参考になるかは分からんが、、、 開発環境 macOS Catalina 環境作っていくよーん nodenvがpyenvとなんか親戚的な関係らしいので、nodenvを使っていこうと思うが、調べるとanyenv入れてやる方法がとても出てくる。しかし、pyenvはもうダイレクトにbrewから入れてるのでその方法はなんとなく気持ち悪い、、、。よし、nodenvもbrewから直接入れよう!! brew install nodenv よし、パスを通そう、私のMacはzshなので.zprofileに書いていく。調べると.zshrcで書いてる人も結構いる、、、。どっちがいいかはよくわかっていない。誰か教えてください。 ~/.zprofile # 適当に追記 # nodenv export PATH="$HOME/.nodenv/bin:$PATH" eval "$(nodenv init -)" もうこれで環境構築大体終わったも同然。あとはpyenv的な感じでnodenvに使いたいversionを入れていく。installできるversionを確認するには、こうやってやるっぽい。やっぱりpyenvと似てて使いやすい nodenv install --list 今回は、なんとなく15.13.0を入れていく。インストールするには以下の方法で、入れたあとはrehashした方がいいらしい nodenv install 15.13.0 nodenv rehash これで入ってるか確認してみる。 nodenv versions 自分が入れたものが出てきたらOK! 複数のversionを入れてglobal, localでversionを管理することができる。とっても便利。 よし、早速Reactの環境を作っていこう。 任意のディレクトリを作って移動 mkdir test cd test 試しにnodeコマンド使ってみよう、適当なファイルに書いてみて実行してみる sample.js console.log('Hello!'); 作成したら以下のコマンドで実行 node sample.js Hello!が出たら成功です!! よし、Reactやってみよう。Reactの公式によるとnpmではなくnpxで作るのがいいっぽい。 npx create-react-app sample そうするとsampleってディレクトリができてるはず。 cd sample npm start これでなんとReact動いちゃうんですよ。 まあReact以外もできないと意味がないですよね。express入れてみよう。 cd .. mkdir sample2 cd sample2 このsample2のディレクトリにexpress入れていこう、まずはnpm initでnpm環境を初期化、その後にnpm installでexpressを入れていく。 npm init npm install express これだけ!!これでexpressがこのディレクトリに入っちゃった。 追加したいパッケージはnpm installで入れていくことができる。 番外編 番外編って言っていいのか分からないが、npmとは別にyarnってやつもある。 どちらもNodeのパッケージマネージャーだが、デフォルのままだとyarnは使えない。yarnの方がインストールが早く、セキュリティが高い。こんなの入れるしかないでしょ!ってことで入れていきます!! まず、.nodenv/pluginsと言う場所にyarnを入れていくのだが、デフォルトだとないこともある。ってことで作成していく、確認してある場合は必要はない。 mkdir -p "$(nodenv root)/plugins" そうしたら、そのディレクトリに git clone https://github.com/pine/nodenv-yarn-install.git を実行し入れていく。これでyarnを入れる準備が整った。しかしこれでは準備が整っただけでまだ入っていないので、一度installしたversionをuninstallしてinstallしていく。 nodenv uninstall 15.13.0 nodenv install 15.13.0 これで自動的にyarnが入っていく。 一応確認する方法は、 which yarn これで入っていればOK! これでyarnでもNode開発することができる! まとめ いかがだったでしょうか。自分に向けた備忘録だったので、走り書きで拙い文章だったのですがお役に立てれば幸いです。もっといい方法とかあるかもしれないので、調べ次第追記しようかな、、、気が向けば。。。 参考文献 https://qiita.com/ttokdev/items/3547587b0494dd624901 https://qiita.com/Hai-dozo/items/90b852ac29b79a7ea02b https://ja.reactjs.org/docs/create-a-new-react-app.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コールバック関数って使用頻度高いのに今までちゃんと理解できていなかったのでまとめます。。

コールバック関数とは コールバック関数は他の関数に引数として渡される関数で、外側の関数で何らかの処理やアクションを実行します。(MDN参照:https://developer.mozilla.org/ja/docs/Glossary/Callback_function) MDNの説明では、私には意味がさっぱり理解できませんでした。( ; ; ) いろいろ調べた結果、要は、 コールバック関数とは、高階関数に渡す関数のこと。 高階関数とは、関数に関数を渡すことが出来るやつ。 はい。ここまで調べて見ても私にはしっくりきません? 要の要は、 コールバック関数は、 ボタンクリックした時に〇〇を実行して! 〇秒後に〇〇を実行して! というように、今じゃなくて、任意のタイミングで何かを実行してほしい時に使える便利なものっていう理解に留まりました。(果たしてこの理解は合っているのかどうか...間違っていたらごめんなさい?) 構文 構文その1(関数宣言で書くとこれ) function 高階関数(コールバック関数){ //処理 コールバック関数(); }; 構文その2(無名関数/匿名関数で書くとこれ) const 高階関数 = function(コールバック関数){ //処理 コールバック関数(); }; 構文その3(アロー関数で書くとこれ) ※アロー関数は、...=(コールバック関数)=>{...の()括弧は、中のコールバック関数(引数)が1つのみであれば、()括弧の記載を省略出来る。 const 高階関数 = (コールバック関数)=>{ //処理 コールバック関数(); }; 使用例 「おにぎりの具は何が好き?」というダイアログが表示されて、「梅」と入力した場合のみ、「だと思った!」とコンソールに表示するというものを実装しました。(自分でも意味わからないのですが、思いつくままに作ったら、こんなものになりました?) 高階関数 -> onigiri コールバック関数 -> (fn) window.prompt -> テキストを入力することを促すメッセージを持つダイアログを表示(参照: https://developer.mozilla.org/ja/docs/Web/API/Window/prompt) function onigiri(fn) { const input = window.prompt("おにぎりの具は何が好き?"); if (input === "梅") { fn(); } } onigiri(function () { console.log("だと思った!"); }); setTimeoutメソッドという、「〇秒後に表示する」というメソッドを利用して、5秒後にコンソールに「Hello!」と表示するものを実装してみました。 setTimeout(function () { console.log("Hello!"); }, 5000); まとめ 高階関数、コールバック関数について、勉強してみましたが、まだまだ完全理解には届いていないような気がします。 コールバック関数について調べていると、「同期処理、非同期処理」や「promise」など初めて耳にする用語が次から次に現れて、コールバック関数の奥の深さを知らしめられました? 本投稿の内容に至らない点も多々あると思いますが、ご了承下さい。いつも皆さんが投稿して下さるQiitaの投稿を見て勉強させて頂いてます!プログラミングの勉強これからも頑張りましょう✨ 参考文献 初学者にもとても分かりやすく説明してくれている Youtuberさんのチャンネルです♪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react、vue、next.jsなどの簡易的環境構築〜codesandbox〜

完全に私的な備忘録として記載しています。 ご理解のほど、よろしくお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsにNode.jsをインストールせず、また環境変数の設定を変更せずにNode.js + React環境を構築する方法

目的 Node.jsを利用する機会が増えてきたが、会社のパソコンはセキュリティ上インストーラーを利用して自由にソフトをインストールすることが出来ない。 その為、インストーラを利用せずにNode.jsを構築する必要がある。 方法を調べたのでその内容を簡単に以下に纏める。 手順 node.jsのZipをダウンロードする <ダウンロードサイト> https://nodejs.org/ja/download/ ここでは現時点(2021/04/12)での最新のLTS「v14.16.1」を利用することとする。 適当なフォルダにダウンロードしたzipを展開する ここでは、「C:\00_myspace\node-v14.16.1-win-x64」に展開するものとする コマンドプロンプトを起動して、フォルダを関係変数パスに設定する set PATH="C:\00_myspace\node-v14.16.1-win-x64";%PATH% 展開したフォルダに移動する cd C:\00_myspace\node-v14.16.1-win-x64 reactをインストールする npm install -g create-react-app 引き続きコマンドプロンプト上でreactプロジェクトを作成する create-react-app testreact 作成したフォルダへ遷移する cd testreact サーバーを起動する npm start これで構築は完了
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js、Rails とFirebase AuthenticationでJWT認証を実装する

はじめに JWT認証に関して、Next.js Rails Firebase Authenticationという組み合わせでの実装をした記事があまりないとのことなので、今回は実際にやっていこうと思う。 ちなみに、今回は https://simple-minds-think-alike.hatenablog.com/entry/rails-firebase-authentication を参考にしながらやっていく。 実際のファイル構成等は下記から閲覧していただけると幸いです。 (まだアプリ自体は作成中なので悪しからず・・・) https://github.com/yumaasato/m-api https://github.com/yumaasato/m-frontend 環境 Ruby: 2.6.5 Rails 6.0.3.6 firebase-auth-rails Redis *ちなみに今回、user認証でよく用いられるdeviseは必須ではありません。 今回は使わずに実装を進めていきます。 1. Redisをインストールする mac版の場合は、 $ brew install redis-server Linuxでは、 $ sudo apt install redis-server ですかね。 内部でfirebase_id_tokenを使っているためこのRedisをインストールする必要があるとのことです。 2. firebase-auth-railsの追加 今回、firebase-auth-railsというgemを利用することでjwt認証を比較的簡単に行うことができます。 Gemfile gem 'firebase-auth-rails' を追加します。 そして、 $ bundle install を実行します。 3-1. 実装(プロジェクトの設定) まずはじめに、Reidsとfirebaseプロジェクトの設定をおこないます。 config/initializers/firebase_id_token.rb FirebaseIdToken.configure do |config| config.redis = Redis.new config.project_ids = [ENV['FIREBASE_PROJECT_ID']] end 3-2. uidを追加 firebaseのUIDを保存するカラムを追加します。 $ rails g migration AddUidToUsers uid:string その後、 $ rails db:migrate を実行する。 3-3. Application Controller api/v1側にApplication Controllerを app/controllers/api/v1/application_controller.rb module Api class V1::ApplicationController < ActionController::API include Firebase::Auth::Authenticable before_action :authenticate_user end end としており、元のApplication Controllerについては触れられていないが、 私は下記のようにした ```rails:app/controllers/application_controller.rb class ApplicationController < ActionController::Base end つまり、application_controller.rbを2つ書いたというわけだが、これで正しいのかは正直怪しい。 しかし、しっかり実装できているため、一つの参考にはなるだろう。 3-3. ユーザー登録用のコントローラー実装 次に、ユーザー登録用のコントローラー実装する。正直名前は何でもよいが、今回はusers_controller.rbとする。 app/controllers/pi/v1/auth/users_controller.rb require_dependency 'api/v1/application_controller' module Api module V1 module Auth class UsersController < V1::ApplicationController skip_before_action :authenticate_user def create FirebaseIdToken::Certificates.request raise ArgumentError, 'BadRequest Parameter' if payload.blank? @user = User.find_or_initialize_by(uid: payload['sub']) do |user| user.name = payload['name'] end if @user.save render json: @user, status: :ok else render json: @user.errors, status: :unprocessable_entity end end private def token params[:token] || token_from_request_headers end def payload @payload ||= FirebaseIdToken::Signature.verify token end end end end end 今回は、Userのnameにnull:falseと設定しているため、 @user = User.find_or_initialize_by(uid: payload['sub']) do |user| user.name = payload['name'] end という記述になっている。 そうでない場合は、参考記事通りに @user = User.find_or_initialize_by(uid: payload['sub']) でもよいかと思われる。 3-4. Routeの追加 私は、下記の通りに実装した。 app/config/routes.rb Rails.application.routes.draw do namespace 'api' do namespace 'v1' do resources :players resources :games, only: %i(index) namespace 'auth' do post 'users' => 'users#create' end end end end 4-1. 実装後のオペレーション(重要) 実装後、$ rails cとして、firebase_id_tokenのDownloading Certificates以下に記載してある内容を実行する必要がある。 irb(main):001:0>FirebaseIdToken::Certificates.request ・ ・ ・ irb(main):002:0>FirebaseIdToken::Certificates.present? => true となってはじめて、jwt認証を使うことができる。 5-1. Next側の実装(補足) 次にNext.js側の実装を行う。フロント側の実装方法が省かれている記事が多いが、ここではNext側についても実装方法を記述する。 詳しい記述方法は https://github.com/yumaasato/m-frontend  を参照していただきたい。 あくまで参考だが、pages/login/index.tsxで export default function LoginPage() => { const [user, loading] = useAuthState(getAuth()); const router = useRouter();   ・   ・   // google認証 const signInGoogle = async () => { await auth.signInWithPopup(provider).catch((err) => alert(err.message)); router.push('/') }; const handleGoogleLogin = () => { const request = async () => { const user = await signInGoogle(); const auth = getAuth(); const currentUser = auth.currentUser; if (auth && auth.currentUser) { const token = await currentUser.getIdToken(true); const config = { token }; try { await axios.post('/api/v1/auth/users', config); } catch (error) { console.log(error); } } }; request(); }; } return ( <div className='w-2/3 py-20 mx-auto'> <p className='font-bold text-4xl py-5 text-center'>Login</p> <Button fullWidth variant="contained" color="default" className={classes.submit} startIcon={<CameraIcon />} onClick={handleGoogleLogin} > SignIn with Google </Button> </div> ); とすればいいのではないか。 これは、signInGoogleでfirebase AuthでのGoogle認証を行い、 handleGoogleLoginでRails側にAPIのデータを送るというものである。 ちなみに、ここでは axios.defaults.baseURL ='http://localhost:3000' としている。 これで一通りの実装は終えたことになる。 補足 Railsにおいて、.envファイルで環境変数を管理する際には、 gem 'dotenv-rails' をインストールする必要があるため、忘れずに設定しておこう。 (これで認証に時間がかかったので・・・) おわりに エンジニアの方からすれば、この認証は大したことはないのかもしれないが、記事を参考にしながら実装を進めている初学者からすれば、なかなか骨の折れる作業である。事実、私もこの実装だけでかなりの時間を費やしてしまった。 私と同じようにNext.js、Rails とFirebase AuthenticationでのJWT認証で困っている人たちの助けになれば幸いである。また、どこか誤りがありましたら、伝えてもらえると助かります。最後まで閲覧していただきまして、誠にありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】useStateで直前のデータを保存しつつ、新しいデータを挿入する方法

React学習で頭がパンクしていくのを防いでいくために今日もアウトプットします。 Reactの非常に便利なuseState Vueを使った後だとReactは本当に保守性に優れているなと日々感じます。 そんなuseStateで直前のデータを保存しつつ新たにデータを挿入する方法をお教えします。 やり方としてはスプレッド構文というものを使います。 とりあえず例を見ていきましょう! const [students, setStudents] = useState([]) setStudents(prevState => [...prevState, {name: 'Taro', gender: '男'}]); このようにprevStateを引数にとり、...prevState(スプレッド構文)を使って直前のデータを引き取りそのまま追加しています。 これを行わないとstudentsの中身が全て初期化されて上書きされてしまうのでとても役に立つっていう話です。 まぁこんな感じで今日のアウトプットは終わります。 Thank you for reading
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

5分で出来るかな? React/TypeScript/Next.js/Material-ui プロジェクトテンプレートの簡単な作り方

はじめに React/TypeScript/Next.js/Material-ui のプロジェクトテンプレートがあれば便利かなと思ったので作ってみました。 あと、手順をまとめれば5分で出来ると思ったのでやってみました。 いってみよう。 完成形はこれ 環境 % sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 # Catalinaさん BuildVersion: 19H2 % brew -v Homebrew 3.0.11 % nodebrew -v nodebrew 1.0.1 % node -v v14.16.1 % yarn -v 1.22.10 開始 1. Next.js/TypeScript プロジェクトの作成 1-1. 開発用のディレクトリ作るよ(ご自由にどうぞ) mkdir ~/Development 1-2. ディレクトリ移動するよ cd ~/Development 1-3. Next.js/TypeScript プロジェクトの雛形を作るよ yarn create next-app react-sample-app --example with-typescript ここまでですでに3分半経過してます。yarn(^q^) 1-4. ディレクトリ移動するよ cd react-sample-app 1-5. sample-app ディレクトリの中身はこうなってるよ . ├── .git # ギットさん ├── components # UIコンポーネント置き場 ├── interfaces # 共有インターフェース置き場 ├── node_modules # このプロジェクトで利用するモジュール達 ├── pages # サイトページ置き場 ├── utils # 共通関数などなど置き場 ├── .gitignore # イグノアさん ├── README.md # リードミーさん ├── next-env.d.ts # Next.jsタイプがTypeScriptコンパイラによって確実に取得されるようにします。ですって ├── package.json # このプロジェクトで利用するモジュールを管理するやつ1 ├── tsconfig.json # TypeScriptの設定ファイル └── yarn.lock # このプロジェクトで利用するモジュールを管理するやつ2 1-6. 動かしてみるよ yarn run dev 1-7. http://localhost:3000/ にアクセスしてみるよ (これを触ってソース見るだけで勉強になるよ) 1-8. 停止するよ control + c 2. Material-UI を使うよ Material-UI を Next.js の SSR に対応させてくよ 2-1. Material-UI をインストールするよ yarn add @material-ui/core @material-ui/icons 2-2. theme.ts を作成するよ, ソースはこちらを参照 vi components/theme.ts 2-3. _app.tsx を作成するよ, ソースはこちらを参照 vi pages/_app.tsx 2-4. _document.tsx を作成するよ, ソースはこちらを参照 vi pages/_document.tsx ドロワー付きヘッダーを作成するよ 2-5. components/Layout.tsx を修正するよ, ソースはこちらを参照 vi components/Layout.tsx 3. 起動, Finish!! yarn run dev 結果発表 敗因 我が家のネットワークが遅かったぁ(^q^) でも少ない手順でここまで出来るのは便利ですね 最後に ソースはご自由にお使いください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsのLinkコンポーネントにpassHref属性を付与するのはどんな時なのか調べてみた

Next.jsを用いてクライアントサイドで画面遷移を行う場合、Linkコンポーネントを使用します。LinkコンポーネントはデフォルトでpropsとしてpassHref属性を受け取ることができるのですが、これはいったいどんな場面で使用するんだろう?と思って調べてみました。 使用技術 React Next.js styled-components 結論 Linkコンポーネントの子供がaタグをラップするカスタムコンポーネントの場合にLinkコンポーネントに「passHref」を付与する Next.js | 日本語ドキュメントに書いてありました。 公式ドキュメントやっぱり神! そもそも、passHrefとは何か? Linkコンポーネントがデフォルトで受け取ることができるpropsのうちのひとつ passHrefは子供にhrefプロパティを強制送信する デフォルト値は「false」 passHrefを付与しない場合 「passHref」を付与しないと、aタグにhref属性が付与されない。aタグにhref属性が付与されないと、検索エンジンにリンクとみなされないのでSEOに悪影響を及ぼす可能性がある。だから書き方的によろしくない。 サンプルコード import Link from 'next/link'; import styled from 'styled-components'; // これは、<a>タグをラップするカスタムコンポーネントを作成します const RedLink = styled.a` color: red; `; const NavLink = () => { return ( <Link href="/posts"> <RedLink>投稿リストページへ遷移します</RedLink> </Link> ); }; export default NavLink; 実際の画面 passHrefを付与したらどうなるか Linkの子であるaタグをラップしたカスタムコンポーネントにhref属性が付与されるようになります。 サンプルコード <Link href="/posts" passHref> <RedLink>投稿リストページへ遷移します</RedLink> </Link> 実際の画面 Next.jsの公式ドキュメントに記載されている内容ですし、理由も明白なため、CSS in JSライブラリとしてstyled-componentsを使用している場合は、必ずpassHref属性を付与するようにした方が良さそう 参考文献 Next.js | 日本語ドキュメント Next.js | 公式ドキュメント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsのLinkコンポーネントにpassHref属性を付与するのはどんな時か

Next.jsを用いてクライアントサイドで画面遷移を行う場合、Linkコンポーネントを使用します。LinkコンポーネントはデフォルトでpropsとしてpassHref属性を受け取ることができるのですが、これはいったいどんな場面で使用するんだろう?と思って調べてみました。 使用技術 React Next.js styled-components 結論 Linkコンポーネントの子供がaタグをラップするカスタムコンポーネントの場合に Linkコンポーネントに「passHref」を付与する Next.js | 日本語ドキュメントに書いてありました。公式ドキュメントは神! そもそも、passHrefとは何か? Linkコンポーネントがデフォルトで受け取ることができるpropsのうちのひとつ passHrefは子供にhrefプロパティを強制送信する デフォルト値は「false」 passHrefを付与しない場合 「passHref」を付与しないと、aタグにhref属性が付与されない。aタグにhref属性が付与されないとサイトのSEOに悪影響を及ぼす可能性があります。 サンプルコード import Link from 'next/link'; import styled from 'styled-components'; // これは、<a>タグをラップするカスタムコンポーネントを作成します const RedLink = styled.a` color: red; `; const NavLink = () => { return ( <Link href="/posts"> <RedLink>投稿リストページへ遷移します</RedLink> </Link> ); }; export default NavLink; 実際の画面 passHrefを付与したらどうなるのか レンダリングされたaタグにhref属性が付与されるようになります サンプルコード <Link href="/posts" passHref> <RedLink>投稿リストページへ遷移します</RedLink> </Link> 実際の画面 Next.jsの公式ドキュメントに記載されていることなので、CSS in JSライブラリとしてstyled-componentsを使用している場合は、必ずpassHref属性を付与するようにした方が良さそう 参考文献 Next.js | 日本語ドキュメント Next.js | 公式ドキュメント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?React + TypeScript + Firebaseで認証付きの簡単な掲示板を作ろう

はじめに つぶやきしかできないWebアプリを作りました。ReactでWebサービスを作りたい人はこのチュートリアルを読み進め、足りない機能を作ってみると良いでしょう。 Demo: https://single-board-3c001.web.app/ Code: https://github.com/shuent/single-board Webアプリの機能 ログイン Googleアカウントかメールアドレスで認証。 つぶやき一覧 コメント投稿 認証したユーザーのみ投稿できる 使用する技術・ライブラリなど React Hooks useReducer ContextAPI TypeScript Create React App React Router Firebase Auth Firestore Hosting Chakra UI Hooks 今どきなので、関数コンポーネントとHooksを使います。筆者は関数コンポーネントが出てからReactを勉強したので、Class時代のReactを書いたことがない。 TypeScript コンパイル時エラーを出してくれる IDEがコード補完しやすい というメリットがあるので利用しています。初心者にとってコードを書く量が多くなるというデメリットを差し引いても、メリットが余りあります。 状態管理 状態管理にはHooksのuseReducerと Context APIを利用して、Fluxの思想を取り入れます。Reduxは使いませんが、実装の流れとしては一緒なので使い方は一度見ておくと良さそうです。 UIライブラリ: Chakra UI Chakra UI は TailwindCSS のようなユーティリティベースなComponentを提供するUIライブラリです。Reactコンポーネントになっているので、Tailwindより使いやすく、Material UIなどのUIフレームワークよりは自由度があるので好きです。 Firebase Firestore 今回はデータ構造が簡単なため、NoSQLであるFirestoreを利用します。NoSQLはDB設計に正解がないので難しく、複雑なリレーションを張るには向いてません。反面、バックエンドが要らず手軽に利用できるので、小規模で単純なデータ構造のアプリには使いやすいです。 Firebase Authentication 認証にはFirebase Authentication を利用します。tokenの管理などを裏でやってくれるので、とても楽です。さらにFirebase UIを使い、ログイン画面もほぼコード書かずに済みました。 Hosting コマンド一つでデプロイ、urlを発行してくれます。今回はこれを使います フロントエンドのホスティングサービスは他にもいろいろ出ています。Vercel, Netlify, Amplify. どれも簡単にデプロイできるので、試してみてください。 実装手順 画面設計・機能を書き出す データモデルの型を書き出す コンポーネント構造を考える 構造化して各フォルダ・ファイルを作る 画面を実装する。表示するのはダミーデータ ユーザー認証を実装 Flux(useReducer + Context)でつぶやきの状態管理を実装 firebaseから 読み取り、書き込みをできるようにする firestoreのルールを実装する デプロイする 次章から、ハンズオン形式でチュートリアルを書いていきます。コードを全て書いているわけではないので、説明が足りない部分はGithubリポジトリを参照してください。もしわからない部分があれば、質問していただけると記事の改善につながります。 画面設計・機能の書き出し どんなアプリを作るにしても設計が大事です。まず一言で何を作るかを決めます。 「ちょっと見た目に気を使ったシンプルなつぶやき投稿アプリ: Single Board」 機能一覧 次にアプリに欲しい機能を決めます。Single Boardは世に出す意識がなかったので、最低限と練習したい機能をつけることにしました。 投稿一覧が見れる 投稿にはユーザー情報と作成日時、つぶやき内容を載せる ログインしないと投稿できない googleとメールアドレスでログインできる 画面設計 紙でもUIツールでもパワポでも、ラフで良いので、画面を作ります。実際作ったのがこのくらいラフ。笑 トップページと、投稿コンポーネントを描いています。 どうせUIライブラリに依存することになるので、丁寧にSketchやFigmaを使ってデザインする必要はないです。画面数が多い場合、雑にプロトタイプとしてFigmaかなんかで作ってみるのはアリ。 実装 ここからは実装していきます。まず、create-react-app(CRA)でプロジェクトを作成します。 npx create-react-app single-board --template typescript CRAでは必要なライブラリが全部入っているので、設定なしにコードを書き始められます。eslintも入っています。 ただ、コードフォーマットツールのprettierはありません。自動でコードを綺麗に整形したい人はインストールしましょう。vscodeを使っている人は、prettierの拡張機能をインストールすればnpm installする必要がありません。.prettierrcで自分の設定でコードフォーマットしてくれます。 参考: https://create-react-app.dev/docs/setting-up-your-editor/#formatting-code-automatically https://www.digitalocean.com/community/tutorials/how-to-format-code-with-prettier-in-visual-studio-code-ja 手元でできた画面を確認してみてください。デフォルトの画面が立ち上がります。 npm start プロジェクトフォルダの中の、基本的にはsrc/の中にコードを書いていくことになります。 それぞれの初期ファイルの説明は公式ページを読んでみてください。 https://www.digitalocean.com/community/tutorials/how-to-format-code-with-prettier-in-visual-studio-code-ja データモデルの型を書き出す プロジェクトを作った時、何から書いていけば良いか迷いますよね。データモデルから書くことによって、データ中心にアプリを作っていけるのでおすすめです。今回は、 つぶやき一覧でユーザー名とつぶやきのデータを表示する。 つぶやきを投稿する という機能に必要なデータモデルを定義します。models.tsというファイルを作成します。 . └── src/ └── models.ts models.ts export type IUser = { displayName: string | null | undefined photoURL: string | null | undefined } export type IComment = { user: IUser content: string createdAt: Date id: string } export type ICommentAdd = { user: IUser content: string } 表示に使う属性だけ定義します。 コンポーネント構造を考える 次にView、見た目の部分を作っていきます。Reactで開発する上で大事な考え方が、コンポーネント志向です。画面を適切な役割ごとにコンポーネントで切り分けて実装することで可読性、保守性が上がります。 コンポーネントの種類には2種類あります。 APIと通信したり、状態管理コードを呼んだり、状態を持っていたり、という副作用を持った実体コンポーネント 受け取ったpropsを表示する純粋な関数コンポーネント (Hooksを使ってもそのコンポーネント内で閉じているものも含む) 私が今回アプリを作っていくときには、 まず画面をざっくり前者の実体コンポーネント(と名付けてみる)で分けてみる。 実体コンポーネントを実装する中で共通化できそうなものは関数コンポーネントに分けてみる という風に作っていきました。 component構造. - App - Home - Header - Editor - CommentList - Comment - UserAvatar - Content - Footer - Login - Header - Form 考え方としては、Atomic Designを参考に、簡易化しています。実体コンポーネント、関数コンポーネントはそれぞれ Organism, molecules に対応するかと思います。 大事なのは、難しく考えずだいたいで切り分けてあとで共通化する、ということです。最初からDRYでやるのは悪手です。 実装していきます。 フォルダ・ファイルを構造化して作成する。 先に必要になりそうなファイルを全部作っていきます。 . ├── package-lock.json ├── package.json ├── public ├── src │ ├── App.tsx # 各コンポーネントを呼び出す │ ├── api # firestoreのインターフェース │ │ └── commentsApi.ts │ ├── components │ │ ├── CommentList.tsx │ │ ├── Editor.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ ├── Home.tsx │ │ ├── Login.tsx │ │ ├── MainVisual.tsx │ │ └── UserAvatar.tsx │ ├── contexts │ │ ├── authContext.tsx # ユーザー認証状態管理 │ │ └── commentsContext.tsx # つぶやきの状態管理 │ ├── reducers │ │ └── commentsReducer.ts # つぶやきのFlux (あとで解説) │ ├── firebase.ts │ ├── index.tsx # App.tsxを呼び出しているだけ │ ├── models.ts # データモデル │ └── theme.ts # 全体UIの設定 └── tsconfig.json Viewを作る UIライブラリのChakra UIをインストールします。 npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 react-routerを使い、urlによって、トップ画面とログイン画面を出し分けます。公式ドキュメントではサンプルを動かせるので、めちゃわかりやすいです。 npm i react-router-dom App.tsx import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' import { Header } from './components/Header' import { Login } from './components/Login' import { Home } from './components/Home' function App() { return ( <Router> <Header /> <Switch> <Route exact path='/'> <Home /> </Route> <Route path='/login'> <Login /> </Route> </Switch> </Router> ) } export default App Home.tsx import { CommentList } from './CommentList' import { MainVisual } from './MainVisual' import { Editor } from './Editor' import { Footer } from './Footer' export const Home = () => ( <> <MainVisual /> // 一番上のメインビジュアル <Editor /> // つぶやき編集フォーム <CommentList /> // つぶやきリスト <Footer /> // フッター </> ) ダミーデータを作り、とりあえず表示するの画面を作っていきます。 CommentList.tsx import { HStack, Box, Avatar, Heading, Text } from '@chakra-ui/react' import { IComment, IUser } from '../models' // ダミーデータ const user1: IUser = { displayName: 'testuser1', photoURL: 'sample.jpg' } const dcomments: IComment[] = [ { user: user1, content: 'first comment ss', createdAt: new Date(), id: 'comment1id', }, { user: user1, content: '元気ですか', createdAt: new Date(), id: 'comment2id', }, export const CommentList = () => { return ( <> <Heading> Posted Comments </Heading> <ul> {comments === [] ? ( <p>No Post</p> ) : ( // Comment 実装は省略 comments.map((comment) => ( <Comment key={comment.id} comment={comment} /> )) )} </ul> </> ) } 省略したコンポーネントはレポジトリをみてみてください。 認証画面・機能を作成 ログイン画面を作っていきます。Firebase AuthenticationとFirebaseUIを使うことで簡単に実装できます。 firebaseの設定 firebase consoleでプロジェクトを作成します。 https://console.firebase.google.com/u/0/?hl=ja 作成したら、プロジェクトの設定 > Firebase SDK snippet を取得します。 CRAは元々の設定で、REACT_APP_から始まる環境変数名を.envファイルからアプリに組み込んでくれます。そして、ビルド時に値を埋め込んでくれます。これで外に変数が漏れることはありません。 https://create-react-app.dev/docs/adding-custom-environment-variables/ 先ほど取得した値を変数として.local.envファイルに宣言し、プロジェクトのルートにおきます。 env.local REACT_APP_APIKEY=xxxxxx REACT_APP_AUTHDOMAIN=xxxxxx REACT_APP_PROJECTID=xxxxxx REACT_APP_STORAGEBUCKET=xxxxxx REACT_APP_MESSAGINGSENDERID=xxxxxx REACT_APP_APPID=xxxxxx REACT_APP_MEASUREMENTID=xxxxxx プロジェクト内では、firebaseを扱うファイルを作り、環境変数を埋めます。ついでにFirebaseの認証とデータベースにfirestoreを使うので、exportしておきます。 src/firebase.ts import firebase from 'firebase' const fireConfig = { apiKey: process.env.REACT_APP_APIKEY, authDomain: process.env.REACT_APP_AUTHDOMAIN, projectId: process.env.REACT_APP_PROJECTID, storageBucket: process.env.REACT_APP_STORAGEBUCKET, messagingSenderId: process.env.REACT_APP_MESSAGINGSENDERID, appId: process.env.REACT_APP_APPID, measurementId: process.env.REACT_APP_MEASUREMENTID, } firebase.initializeApp(fireConfig) const auth = firebase.auth() const firedb = firebase.firestore() export { firebase, auth, firedb } firebaseUIを導入 ログイン画面を作っていきます。 firebaseUIのReact用ライブラリがあるのでインストールします。 *公式の開発者がストップしているみたいなので、canary版を使います。 https://github.com/firebase/firebaseui-web-react/pull/122 npm install react-firebaseui@canary ログインコンポーネントを作ります。ログインフォームの挙動はuiConfig変数で設定します。 src/Login.tsx import { Center, Heading, VStack } from '@chakra-ui/layout' import { primaryTextColor } from '../theme' import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth' import { firebase, auth } from '../firebase' const uiConfig = { signInFlow: 'popup', signInSuccessUrl: '/', signInOptions: [ firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.EmailAuthProvider.PROVIDER_ID, ], } export const Login = () => { return ( <Center mt={8}> <VStack> <Heading size='md' color={primaryTextColor}> Sign In </Heading> <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth} /> </VStack> </Center> ) <StyledFirebaseAuth firebaseAuth={auth} />でプロジェクトのfirebaseインスタンスとUIをつなげています。 AuthContextで認証状態管理 ログイン・登録ができるようになったので、セッション情報:(「ログインしているかどうか」と「ログインしているユーザー情報」)をアプリ内で使えるようにします ユーザー認証の状態管理には、Context APIを使用します。流れとしては、Contextを作成し、Providerで状態を保存し、useContextで使います。 公式: https://ja.reactjs.org/docs/context.html 認証用のContextを扱う、authContext.tsを作成します。 src/contexts/authContext.tsx import React, { createContext, useContext, useState, useEffect } from 'react' import { firebase, auth } from '../firebase' type AuthContextProps = { user: firebase.User | null } const AuthContext = createContext<AuthContextProps>({ user: null, }) export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState<firebase.User | any>(null) const [loading, setLoading] = useState(true) useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { setUser(user) setLoading(false) }) return unsubscribe }, []) return ( <AuthContext.Provider value={{ user }}> {!loading && children} </AuthContext.Provider> ) } export const useAuth = () => { return useContext(AuthContext) } auth.onAuthStateChangedではユーザーの認証状態を監視して、ログイン、ログアウト時、と認証情報が変わる度に引数に渡しているコールバック関数を実行します。 ProviderをUnmountする時に監視を捨てる必要があるので、useEffectの返り値に設定してます。 https://firebase.google.com/docs/auth/web/manage-users?hl=ja セッションを使用する 今定義した関数を使い、アプリ上でセッションを取得できるようにしましょう。アプリ全体をAuthProviderで囲みます。これで囲んだどのコンポーネント内でもuseAuth()が使えることになります。 App.tsx ... import { AuthProvider } from './contexts/authContext' function App() { return ( + <AuthProvider> <ChakraProvider theme={theme}> <Router> <Header /> <Switch> <Route exact path='/'> <Home /> </Route> <Route path='/login'> <Login /> </Route> </Switch> </Router> </ChakraProvider> + </AuthProvider> ) } ヘッダーでログインしている時はログアウトボタン、ログインしていないときはログインリンクを表示します。 Header.tsx import { Link } from 'react-router-dom' import { useAuth } from '../contexts/authContext' import { auth } from '../firebase' export const Header = () => { const { user } = useAuth() return ( <> // ...省略 {user ? ( <Text as='button' onClick={() => auth.signOut()}> Log Out </Text> ) : ( <Link to='/login'> <Text color='white'> Sign In</Text> </Link> )} // ... </> ) トップ画面のエディターでもログインしている時のみ投稿できるようにします。 src/Editor.tsx export const Editor = () => { const { user } = useAuth() const [content, setContent] = useState('') const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (content !== '' && user) { // post content to server } else if (!user) { alert('Sign in first') } setContent('') } const handleChange = (e: React.FormEvent<HTMLTextAreaElement>) => { setContent(e.currentTarget.value) } return ( <div> <VStack as='form' onSubmit={handleSubmit} > <Textarea name='content' value={content} onChange={handleChange} placeholder="What's on your mind?" /> <Button type='submit' colorScheme='orange'> post </Button> </VStack> </div> ) } これで認証状態管理は終わりです。 つぶやきの状態管理 つぶやきの状態管理でもContextAPIを使い、状態を保持できるようにします。加えて、useReducerというHookを使いFluxアーキテクチャでの状態管理を行います。Contextだけでも管理できないことはないですが、状態を変更する機能が多くなってきた時に分かりやすいです。 reducerから定義していきます。 src/reducers/commentsReducer.ts import { IComment } from '../models' export type CommentsAction = | { type: 'SET_COMMENTS'; comments: IComment[] } | { type: 'ADD_COMMENT'; comment: IComment } export type CommentsState = { comments: IComment[] } export const initialState: CommentsState = { comments: [], } export const commentsReducer = ( state: CommentsState, action: CommentsAction ): CommentsState => { switch (action.type) { case 'SET_COMMENTS': return { comments: action.comments } case 'ADD_COMMENT': return { comments: [action.comment, ...state.comments] } default: return state } 後々firestoreにつぶやきを投稿したタイミングでまたつぶやきリストを取得するかストリーミングすれば最新の状態になるので、ADD_COMMENTはあってもなくても良いのですが、毎回APIを呼ばなくても良いようにと、練習のために作っています。 Contextを作ります。 src/contexts/commentsContext.tsx import { createContext, Dispatch, ReactNode, useReducer, useContext, } from 'react' import { CommentsAction, commentsReducer, CommentsState, initialState, } from '../reducers/commentsReducer' type CommentsContextProps = { state: CommentsState dispatch: Dispatch<CommentsAction> } const CommentsContext = createContext<CommentsContextProps>({ state: initialState, dispatch: () => initialState, }) export const CommentsProvider = ({ children }: { children: ReactNode }) => { const [state, dispatch] = useReducer(commentsReducer, initialState) return ( <CommentsContext.Provider value={{ state, dispatch }}> {children} </CommentsContext.Provider> ) } export const useComments = () => useContext(CommentsContext) EditorとCommentListで使うので、それらを含むHomeコンポーネントで囲んでおきます。 src/Home.tsx export const Home = () => ( <> + <CommentsProvider> <MainVisual /> <Editor /> <CommentList /> <Footer /> + </CommentsProvider> <> ) フォーム送信時にDispatchします。 src/components/Editor.tsx export const Editor = () => { const { user } = useAuth() const { dispatch } = useComments() const [content, setContent] = useState('') const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (content !== '' && user) { const toPost: ICommentAdd = { user: { displayName: user.displayName, photoURL: user.photoURL }, content, } dispatch({ type: 'ADD_COMMENT', comment: { ...toPost, createdAt: new Date(), id: Date(), }, }) } else if (!user) { alert('Sign in first') } setContent('') } return (...) } useEffectでコンポーネントを読み込むタイミングでDispatchします。 src/components/CommentList.tsx export const CommentList = () => { const { state, dispatch } = useComments() const dcomments: IComment[] = [ { user: user1, content: 'first comment', createdAt: new Date(), id: 'comment1id', }, { user: user1, content: '元気ですか', createdAt: new Date(), id: 'comment2id', }, ] useEffect(() => { let unmount = false if (!unmount) { console.log('set comments called') dispatch({ type: 'SET_COMMENTS', comments: dcomments }) } return () => { unmount = true } }, [dispatch]) return (...) } これでつぶやき(コメント)の状態管理は終わりです。 firestore への read/write firestore上でデータを管理できるようにします。 firebaseコンソールで firestoreを有効にします。 firestoreへのインターフェースを実装します。ここで実装することで、将来別のAPIを使った時にも 関数名、引数、返り値を同じにすることでView側を変更しなくても良いように、疎結合に実装します。もっと厳密にやるならinterfaceを定義したり、Dipendency Injectionをすることになります。 src/api/commentsApi.ts import { firedb, firebase } from '../firebase' import { IComment, ICommentAdd } from '../models' export const getComments = async () => { const snapShot = await firedb .collection('comments') .orderBy('createdAt', 'desc') .get() const data = snapShot.docs.map<IComment>((doc) => ({ user: doc.data().user, content: doc.data().content, createdAt: doc.data().createdAt.toDate(), id: doc.id, })) return data } export const addComment = async (comment: ICommentAdd) => { return firedb.collection('comments').add({ user: comment.user, content: comment.content, createdAt: firebase.firestore.Timestamp.now(), }) 使用時には、主にDispatch呼び出し前におき、結果をDispatchに渡します。 コメントリスト src/components/CommentList.tsx export const CommentList = () => { const { state: { comments }, dispatch, } = useComments() useEffect(() => { + getComments().then((data) => { + dispatch({ type: 'SET_COMMENTS', comments: data }) + }) }, [dispatch]) return (...) } エディター src/components/Editor.tsx export const Editor = () => { const { user } = useAuth() const { dispatch } = useComments() const [content, setContent] = useState('') const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (content !== '' && user) { const toPost: ICommentAdd = { user: { displayName: user.displayName, photoURL: user.photoURL }, content, } + addComment({ ...toPost }) dispatch({ type: 'ADD_COMMENT', comment: { ...toPost, createdAt: new Date(), id: Date(), }, }) } else if (!user) { alert('Sign in first') } setContent('') } ブラウザでつぶやいてみると、firestoreにもデータが追加されているのが分かります firestore rule Editor Componentで、ユーザーではない場合投稿できないようにしましたが、直接APIを知られてしまった場合、投稿できてしまいます。さらに今のままだと投稿するユーザー名を偽装して、本人以外の名を騙り投稿できてしまいます。 そのようなことがないように、コンソールでruleを書くことで、セキュリティを守ります。ローカル環境で書いてデプロイすることも可能ですが、ここではコンソールに直接書いてます。 左下のルールプレイグラウンドでは、いろいろな条件でルールをテストできるので、試してみると良いです。 今回のルールはこちら 'comments'以外のリソースにアクセスできない (ログインしてなくても)誰でも読めるようにする ユーザー名と一致する投稿のみ受け付ける。 更新、削除は受け付けない firestore.rule rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { match /comments/{comment} { allow read: if true; allow create: if request.auth.token.name == request.resource.data.user.displayName } } } } 公式: https://firebase.google.com/docs/rules/basics?hl=ja Firebase Hosting へデプロイ ここまででアプリが完成したらFirebase Hostingサービスにデプロイします。 コンソールからHostingを有効にします。 # firebase cliをインストールして、deployコマンドを使えるようにします。 npm install -g firebase-tools # 認証してコンソールで作ったプロジェクトを選択します。 firebase login firebaseのファイルを作成します。 firebase init いろいろ聞かれます。Hostingだけ選択し、 What do you want to use as your public directory? には buildを指定します。 あとは好きなものを選んでください。 最終的にこんなファイルができていれば大丈夫です。 firebase.json { "hosting": { "public": "build", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } Githubと連携してCICDやプルリクでデプロイしてくれるのですが、その設定は扱いません。調べてみてください。 プロジェクトをビルド後、デプロイします! npm run build firebase deploy --only hosting うまくいけば、ターミナルに出てくるurlがデプロイ先です!!! 終わりに 今回作ったアプリにはつぶやきの削除、ユーザー設定、ユーザーページなど機能が足りません。ここまで読んでくれた方はこれを発展させて、改造させて、面白いものを作ってみてください。もし作った時はコメントから報告してくれると嬉しいです。 初めて包括的な記事を書いたので足りないところはあると思いますが、楽しんでいただけたら何らかのアクションをしてくれると嬉しいです。ここまで読んでくれてありがとうございました。次回はもうちょっと高度なことか、コンポーネント設計に関することを書きたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?React + TypeScript + Firebaseで認証付きの簡単な掲示板を作ろう[wip]

はじめに つぶやきしかできないWebアプリを作りました。ReactでWebサービスを作りたい人はこのチュートリアルを読み進め、足りない機能を作ってみると良いでしょう。 Demo: https://single-board-3c001.web.app/ Code: https://github.com/shuent/single-board Webアプリの機能 ログイン Googleアカウントかメールアドレスで認証。 つぶやき一覧 コメント投稿 認証したユーザーのみ投稿できる 使用する技術・ライブラリなど React Hooks useReducer ContextAPI TypeScript Create React App React Router Firebase Auth Firestore Hosting Chakra UI Hooks 今どきなので、関数コンポーネントとHooksを使います。筆者は関数コンポーネントが出てからReactを勉強したので、Class時代のReactを書いたことがない。 TypeScript コンパイル時エラーを出してくれる IDEがコード補完しやすい というメリットがあるので利用しています。初心者にとってコードを書く量が多くなるというデメリットを差し引いても、メリットが余りあります。 状態管理 状態管理にはHooksのuseReducerと Context APIを利用して、Fluxの思想を取り入れます。Reduxは使いませんが、実装の流れとしては一緒なので使い方は一度見ておくと良さそうです。 UIライブラリ: Chakra UI Chakra UI は TailwindCSS のようなユーティリティベースなComponentを提供するUIライブラリです。Reactコンポーネントになっているので、Tailwindより使いやすく、Material UIなどのUIフレームワークよりは自由度があるので好きです。 Firebase Firestore 今回はデータ構造が簡単なため、NoSQLであるFirestoreを利用します。NoSQLはDB設計に正解がないので難しく、複雑なリレーションを張るには向いてません。反面、バックエンドが要らず手軽に利用できるので、小規模で単純なデータ構造のアプリには使いやすいです。 Firebase Authentication 認証にはFirebase Authentication を利用します。tokenの管理などを裏でやってくれるので、とても楽です。さらにFirebase UIを使い、ログイン画面もほぼコード書かずに済みました。 Hosting コマンド一つでデプロイ、urlを発行してくれます。今回はこれを使います フロントエンドのホスティングサービスは他にもいろいろ出ています。Vercel, Netlify, Amplify. どれも簡単にデプロイできるので、試してみてください。 実装手順 画面設計・機能を書き出す データモデルの型を書き出す コンポーネント構造を考える 構造化して各フォルダ・ファイルを作る 画面を実装する。表示するのはダミーデータ ユーザー認証を実装 Flux(useReducer + Context)でつぶやきの状態管理を実装 firebaseから 読み取り、書き込みをできるようにする firestoreのルールを実装する デプロイする 次章から、ハンズオン形式でチュートリアルを書いていきます。コードを全て書いているわけではないので、説明が足りない部分はGithubリポジトリを参照してください。もしわからない部分があれば、質問していただけると記事の改善につながります。 画面設計・機能の書き出し どんなアプリを作るにしても設計が大事です。まず一言で何を作るかを決めます。 「ちょっと見た目に気を使ったシンプルなつぶやき投稿アプリ: Single Board」 機能一覧 次にアプリに欲しい機能を決めます。Single Boardは世に出す意識がなかったので、最低限と練習したい機能をつけることにしました。 投稿一覧が見れる 投稿にはユーザー情報と作成日時、つぶやき内容を載せる ログインしないと投稿できない googleとメールアドレスでログインできる 画面設計 紙でもUIツールでもパワポでも、ラフで良いので、画面を作ります。実際作ったのがこのくらいラフ。笑 トップページと、投稿コンポーネントを描いています。 どうせUIライブラリに依存することになるので、丁寧にSketchやFigmaを使ってデザインする必要はないです。画面数が多い場合、雑にプロトタイプとしてFigmaかなんかで作ってみるのはアリ。 実装 ここからは実装していきます。まず、create-react-app(CRA)でプロジェクトを作成します。 npx create-react-app single-board --template typescript CRAでは必要なライブラリが全部入っているので、設定なしにコードを書き始められます。eslintも入っています。 ただ、コードフォーマットツールのprettierはありません。自動でコードを綺麗に整形したい人はインストールしましょう。vscodeを使っている人は、prettierの拡張機能をインストールすればnpm installする必要がありません。.prettierrcで自分の設定でコードフォーマットしてくれます。 参考: https://create-react-app.dev/docs/setting-up-your-editor/#formatting-code-automatically https://www.digitalocean.com/community/tutorials/how-to-format-code-with-prettier-in-visual-studio-code-ja プロジェクトフォルダの中の、基本的にはsrc/の中にコードを書いていくことになります。 それぞれの初期ファイルの説明は公式ページを読んでみてください。 https://www.digitalocean.com/community/tutorials/how-to-format-code-with-prettier-in-visual-studio-code-ja データモデルの型を書き出す プロジェクトを作った時、何から書いていけば良いか迷いますよね。データモデルから書くことによって、データ中心にアプリを作っていけるのでおすすめです。今回は、 つぶやき一覧でユーザー名とつぶやきのデータを表示する。 つぶやきを投稿する という機能に必要なデータモデルを定義します。models.tsというファイルを作成します。 . └── src/ └── models.ts models.ts export type IUser = { displayName: string | null | undefined photoURL: string | null | undefined } export type IComment = { user: IUser content: string createdAt: Date id: string } export type ICommentAdd = { user: IUser content: string } 表示に使う属性だけ定義します。 コンポーネント構造を考える 次にView、見た目の部分を作っていきます。Reactで開発する上で大事な考え方が、コンポーネント志向です。画面を適切な役割ごとにコンポーネントで切り分けて実装することで可読性、保守性が上がります。 コンポーネントの種類には2種類あります。 APIと通信したり、状態管理コードを呼んだり、状態を持っていたり、という副作用を持った実体コンポーネント 受け取ったpropsを表示する純粋な関数コンポーネント (Hooksを使ってもそのコンポーネント内で閉じているものも含む) 私が今回アプリを作っていくときには、 まず画面をざっくり前者の実体コンポーネント(と名付けてみる)で分けてみる。 実体コンポーネントを実装する中で共通化できそうなものは関数コンポーネントに分けてみる という風に作っていきました。 component構造. - App - Home - Header - Editor - CommentList - Comment - UserAvatar - Content - Footer - Login - Header - Form 考え方としては、Atomic Designを参考に、簡易化しています。実体コンポーネント、関数コンポーネントはそれぞれ Organism, molecules に対応するかと思います。 大事なのは、難しく考えずだいたいで切り分けてあとで共通化する、ということです。最初からDRYでやるのは悪手です。 実装していきます。 フォルダ・ファイルを構造化して作成する。 先に必要になりそうなファイルを全部作っていきます。 . ├── package-lock.json ├── package.json ├── public ├── src │ ├── App.tsx # 各コンポーネントを呼び出す │ ├── api # firestoreのインターフェース │ │ └── commentsApi.ts │ ├── components │ │ ├── CommentList.tsx │ │ ├── Editor.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ ├── Home.tsx │ │ ├── Login.tsx │ │ ├── MainVisual.tsx │ │ └── UserAvatar.tsx │ ├── contexts │ │ ├── authContext.tsx # ユーザー認証状態管理 │ │ └── commentsContext.tsx # つぶやきの状態管理 │ ├── reducers │ │ └── commentsReducer.ts # つぶやきのFlux (あとで解説) │ ├── firebase.ts │ ├── index.tsx # App.tsxを呼び出しているだけ │ ├── models.ts # データモデル │ └── theme.ts # 全体UIの設定 └── tsconfig.json Viewを作る react-routerを使い、urlによって、トップ画面とログイン画面を出し分けます。 npm i react-router-dom App.tsx import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' import { Header } from './components/Header' import { Login } from './components/Login' import { Home } from './components/Home' function App() { return ( <Router> <Header /> <Switch> <Route exact path='/'> <Home /> </Route> <Route path='/login'> <Login /> </Route> </Switch> </Router> ) } export default App Home.tsx import { CommentList } from './CommentList' import { MainVisual } from './MainVisual' import { Editor } from './Editor' import { Footer } from './Footer' export const Home = () => ( <> <MainVisual /> // 一番上のメインビジュアル <Editor /> // つぶやき編集フォーム <CommentList /> // つぶやきリスト <Footer /> // フッター </> ) ダミーデータを作り、とりあえず表示するの画面を作っていきます。 CommentList.tsx import { HStack, Box, Avatar, Heading, Text } from '@chakra-ui/react' import { IComment, IUser } from '../models' // ダミーデータ const user1: IUser = { displayName: 'testuser1', photoURL: 'sample.jpg' } const dcomments: IComment[] = [ { user: user1, content: 'first comment ss', createdAt: new Date(), id: 'comment1id', }, { user: user1, content: '元気ですか', createdAt: new Date(), id: 'comment2id', }, export const CommentList = () => { return ( <> <Heading> Posted Comments </Heading> <ul> {comments === [] ? ( <p>No Post</p> ) : ( // Comment 実装は省略 comments.map((comment) => ( <Comment key={comment.id} comment={comment} /> )) )} </ul> </> ) } 以下 todo:4/15までに書き上げる 認証画面・機能を作成 firebaseの設定 firebaseUIを導入 AuthContextで認証状態管理 つぶやきの状態管理 firestore への read/write firestore rule Firebase Hosting へデプロイ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む