Firestore+Authendication+Reactでなにか作る
試すこと
やれたらやる
データ構造
まあ適当なやつで
code:ts
type RecordData = {
title: string; // 記録の名前
start: Date; // 記録の開始時刻
end: Date; // 記録の終了時刻
}
まあ、こんなシンプルなやつでいいだろ
document nameはrecordsにするか。
/icons/typescript.icon対応
シンプル
名前
いい加減決めないと呼ぶのに困りそう
UI
まずは試し試しやっていく
sign in画面は作らない
takker.iconしかlog inできないようにする
2020-11-28 01:52:38 Google loginだと無理みたい
仕方ないので一時的にsign in画面を作っておこう
いや別にmail loginでもいいか
どうしようかな
/icons/google.icon login
pros
securityはこっちのほうが上?
cons
アカウントを再作成する羽目になったときが面倒
アカウント作成画面を復活させないといけない
いや普段は非表示にしておいて、アカウントを作成したいときだけbuildし直せばいいのか
それでも面倒だが
/icons/mail.iconlogin
pros
consoleから手動でaccountを追加でいる
cons
securityが甘い?
emailとpasswordだけ
もしやれたら、stop watchを実装したい
ボタンのon/offでやれるやつ
22:56:33 続きはまた今度
2020-11-28 01:42:09
2020-11-29 23:20:07 いつの間にかforkしちゃってたみたい
01:46:06 log in画面を作る
同時に、firebase consoleでaccountの設定をする
今回はgoogle log inにする
takker.iconだけlog in出来るようにする
01:49:49 mail login以外は、手動でaccountを追加できないみたい
firebase projectは前回と同じのを使う
02:14:50 コピペで実装した
02:15:00 accountの状態によってcomponentを切り替える
すでにlog inしている
そのままページを表示
まだlog inしていない
log in pageにredirectする
とすると、全部のcomponentにaccountの状態をcheckする処理をかぶせる必要が出てくるわけか
これはcomponentとして分離したほうがいいな
どっちがいいかな?
同じpage内でcomponentを切り替える
URLが変化しないのはなんか変かも
Routerでlog in用ページに飛ばす
このとき前のページを覚えておいて、log inに成功したらそこに飛ぶ
各ページのcomponentにuseAuthState()を置く必要がある
後者にした
2020-11-28 15:34:24
後者だと、認証状態を取得する前にdatabaseにaccessしないといけなくなる?
いや、先にuseAuthStateで状態を取得しておき、!user === trueのとき即座にLog in pageにredirectするようにすればいい?
hookの宣言の途中でhistory.push('/Login')を実行して別ページに遷移すると、hookの順番が変わることになりかねないか?
即座にcomponentをunmountしちゃうから別に構わない?
これ読んでから考えるか。
https://gyazo.com/3c165abf608a7b1f7b0519eb8d5cd3c4
なんでだ?
module errorが出るなら、それはIDEで検出できるはず
libraryの問題か?
代わりに素のfirebase APIを使って実装してみる
02:58:32 domainの許可が足りなかった
preview用URLのdomainも入れないとだめみたい
02:59:50 invalidになっちゃった
account作っていないから当然か
いや、account連携の場合は、accountがなければ自動的に作成されるらしい
じゃあ何がいけないんだろう?
03:02:36 signInWithPopupに変えて実行してみる
今度は成功した
signInWithRedirectの使い方が良くなかったのか?
別なタブを開いても問題なし
log in状態は維持されていた
03:10:10 collectionのデータ型を整えた
03:26:37 log in状態でもaccessできなくなった
collection('records').doc(user.uid)にaccessしないといけない
2020-11-29 17:22:21
認証状態を見てredirectするcomponentAuthGuardを作る
認証されていたら、childrenに認証情報を渡してrenderする
どうやって認証情報を渡す?
これが現実的っぽい?
これが参考になりそう
実現したいこと
認証情報がないと絶対にrenderingできないcomponentを作りたい
これをしないと、各component内部に認証が切れたときの処理を書かないといけなくなる
useAuthState()を各componentで呼び出す
log in済みであることを保証できない
認証が必要なページにいる間に認証が切れたときのredirectが面倒
useCollectionDataを呼び出す前にredirectすればいいけど、そうするとhookの順番がわかってしまう
やっぱりPropsから入れ込むしかない
templateに型を渡す
これが妥当か?
これは無理そう
userAuthState()がuseContext()に変わっただけ
個別のcomponentで認証状態をチェックする
できるけど、バカバカしくない?
認証がない時点で最初から呼べないようにしたい
認証されていなかったら、Login pageにredirectする
Reference
描画したいcomponentを受け取り、内部でそいつに認証情報を渡して描画する
20:33:21 useAuthStateの型が何故か死んでいるので、[firebase.User,boolean,firebase.auth.Error,]を指定しておく 21:04:14 力技で実装した
code:AuthGuard.tsx
import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { useAuthState } from 'react-firebase-hooks/auth';
import { authentication } from '../config/firebase';
import firebase from 'firebase';
type AuthData = {
userId: string;
};
interface Props extends Omit<RouteProps, 'component' | 'children'> {
Component: React.FC<AuthData>;
}
export function AuthGuard({ Component, ...props }: Props) {
firebase.User,
boolean,
firebase.auth.Error,
] = useAuthState(authentication);
if (!user) {
return <Redirect to="/login" />;
}
return (
<Route {...props}>
<Component userId={user.uid} />
</Route>
);
}
こういう事して良いのかどうかは全くわからない
T extends AuthDataにしたかったが何故かerrorが出るのでやめた
21:04:43 そういえばfirestoreとauthを使ったappがcodesandboxにあったな
みてみよ
21:06:41 ……なんかすっげー変なことしてる
やはりすぐに読めるような代物ではなかった
21:22:37 なんかloginできなくなった?
loginするaccountを選択した後、popupが閉じるまでだいぶ時間がかかる
閉じられた後何も起きない
多分errorになっている
でもerrorCode出てこないな
21:25:40 domainを追加していなかった
新しいsandboxのpreview windowのdomain
追加して試してみる
/icons/done.icon21:26:55 成功
やっぱりdomainを追加していないだけだった
さっき時間かかっていたのは、codesandbox.ioを経由していたから?
逆につながっていたのが気になる
21:34:40 firestoreを開けない
これなら動く
code:rule
service cloud.firestore {
match /databases/{database}/documents {
match /records/{documents=**} {
allow read, write: if request.auth != null;
}
}
}
これデータ構造に関わるな
やりたいこと
元の認証をクリアすれば、その配下の全てのdataにaccessできるようにしたい
その場合、これはデータ一つ一つに認証情報を入れているので使えない
てことは、collectionを入れ子にするしかないな
/users/{userId}/records/{document=**}というかんじ
21:58:04 認証成功!
/users/{userId}/records/{document=**}にした
https://gyazo.com/0ae9e470578964261ec77ba7f55498a4
https://gyazo.com/4e80e4eeddf4f2b693b6a134b5ab8b6a
22:45:32 /users/{userId}の下にfieldsとしてuser情報入れられるじゃん!
/icons/typescript.icon側の接続方法
code:ts
firestore.collection('users').doc(userId).collection('records'),
{ idField: 'uid' },
);
method chainすればいい
22:01:26 きちんと複数タブ間で同期できている
一方でlog outするともう一方のタブも即座にLog in画面にうつる
22:04:54 dataを追加してみた
userIdは完全に文字列だけで判別されるみたい
誰が作ったとかのメタデータは存在しない
変換する必要があるっぽい?
単純にJSXがDate型等Object型を直接表示する術を持っていないだけだった
とりあえずtoDate().toLocaleString()を挟んでおいた
22:11:49 いい感じのところまで来たぞ!!!takker.icon*3
22:33:31 Detail.tsxを表示できるようにしてみた
更新処理は動くようにはしていない
データ型を別のfileに分離させた
mobileからも動作する。
2020-11-30 20:54:24
やること
23:18:34 やる
23:42:06 これAuthGuardも疎結合にしないとだめか
抽象的な認証情報を渡す
API固有のdataを渡せない
genericを使う
どう指定するんだ?
23:47:25 まあprototypeだし、今そこまで考えなくていいだろ。
適当にやろ
AuthDataをexportして直に依存させるようにした
いい加減どころか文法がおかしい
2020-12-01 00:19:09 適当にダミーの関数を作って渡した
00:18:57 useRecordsを使うようにした
返り値は[]ではなく{}で指定する
記録の追加ボタンを入れる
まずcreateいらないので消す
追加ボタンをどのcomponentに入れるか考える
15:54:18 入力欄が潰れてしまっていたので、min-width: 30pxを指定しておいた
記録開始と記録終了ボタンに変えたい
記録中は、下手にdatabaseに登録せずに、component内で持たせた方が良さそう
記録中に削除されたら困る
記録終了後に登録するようにする
20:50:01 記録開始からの経過時間を表示するようにした
useTimerを作った
21:21:07 実際の記録とtimerで使う記録とで開始時刻を別なDateで使っていたのでややズレが生じていた
同じDateを使うように修正した
21:21:51 だんだん開発への意欲が上がってきたぞtakker.icon
一定のしきい値を越えて、開発しやすくなった気がする
少しの修正を積み重ねてどんどんUXを向上させる段階に突入した
21:04:45 記録開始後に、記録名を入力できるようにした
ふつうの<input>にした
まだややレイアウトがおかしいが、入力できるので十分
00:09:59 記録を終了すると即座に次の記録が開始されるようにした
改善点:最後に記録した時刻からのtimerにしたい
21:05:46 開始時刻の早い順に並ぶようにする
方法
firestore側で並び替えておく
orderByをuseRecordsの実装のなかに挟めば実現できそう
本当は並べ方を外部から指定できるようにしたほうが良いんだろうけど、まあprototypeだし、細かいことは気にしない。
動くことが一番の目標
できた
react tableで並び替える
記録をinteractiveに編集できるようにする
<input>でまずは実装してみる
00:33:18 先にdataの更新追加を行うhookを作っておこう
00:52:07 更新関数を作った
useUpdatorで認証情報を受け取り、記録を更新する関数を返す
2020-12-01 13:12:45 これ困った
cellの中で更新関数を呼び出したい
しかしcellはどのdocumentを更新すれば良いのか知ることが出来ない
13:23:40 どうするか
rowIndexとcolumnIdから元のrecordを特定する関数を作る
pagenationなどを使うと表示上のindexと実際のdataのindexがずれてしまうが、問題なく対処できそう
ずれずに指定できるみたい
何らかの方法でdocIdをCellに渡す
14:08:13 できた
何故か書き込みエラーが生じるのでなんでかと思ったが、引数を間違えていただけだった
/icons/fail.iconconst { setRecord } = useUpdator(userId);
/icons/pass.iconconst { setRecord } = useUpdator({userId});
間違えないように、userIdではなくauthDataでやりとりするようにした
認証情報の中身を知っている必要はない
useEffectを使うことで、初期値の変更に応じて表示値を変えられるようにした
14:30:45 EditableCellを列ごとに使い分けたいな
14:37:53 どうやらColumns<T>[]のCell propertyに指定すれば行けるっぽい
useTable()から注入するoption引数はそのまま使える
あれはFormを前提とした作りになっていて、使いづらい
onBlurでcheckするだけで簡単に作れた
15:33:54 値が変化したときだけonBlurでdatabase更新するようにした
21:19:17 秒まで表示するようにした
21:58:25 使用時間を表示するようにした
01:44:38 うーん、かなりめんどくさい
自前でtable作ったほうが良さそう?
borderを全部0pxにしたらいい感じの見た目になった
15:17:31 divをcontenteditableにした方が見た目は良かったかも
inputだと若干文字が小さい
2020-12-01 12:46:25 時刻だけ表示するようにした
年月日は隠した
流石に生のDateで日付操作するのつらい
記録の選択・削除機能の実装
選択行を削除が難しい
外部に選択行の情報を送ると何故か無限ループに陥ってしまう
回避策
自前で選択処理を実装する
これしかなさそう
22:44:32 Table側にstateを用意した
22:46:47 Tableには番号だけ持たせよう
Idで選択したかどうかを保持するのは重い
Objectを比較しないといけない
RecordIdの内部実装に依存してしまっている
選択範囲が必要になったときに、rowsからIdを照会して渡す
23:17:05 できた
その影響で、選択範囲が削除後に残ってしまっている
これは今後の課題だな
もっとまともなCRUDの実装方法があるはず
List componentに削除ボタンをもたせる
これはやりたくないな
Nav barの実装
別途componentにしよう
<NavBar>でいいや
22:26:01 調べた
22:28:58 作ってる
22:37:08 Dashboard.tsxの一番上のdivに入れたら何も表示されなくなった
入れる場所が指定されているのか?
入れ直したら直った
単にdeployの反映にエラーがあっただけか
22:55:32 なぜかbuttonが表示されない?
logoがないせいか?
中に文字を入れていないだけだった
23:02:45 /icons/javascript.iconでmenuに移動するようにしないといけないみたい
面倒だな
23:16:52 とりあえずそれっぽいの出来た
20:19:26 見にくいので上部に固定した
23:54:09 やめた
上の部品が隠れてしまう
calendar表示の導入
できた
2020-12-02 00:12:24
vercelでdeployしてみた
00:25:03 失敗。
typescriptのversionが低いみたい
最新版に書き換えてdeployしてみる