Firestore+Authendication+Reactでなにか作る
試すこと
やれたらやる
データ構造
まあ適当なやつで
tstype RecordData = {
title: string; // 記録の名前
start: Date; // 記録の開始時刻
end: Date; // 記録の終了時刻
}
まあ、こんなシンプルなやつでいいだろ
document nameは records
にするか。

対応
シンプル
名前
いい加減決めないと呼ぶのに困りそう
UI
まずは試し試しやっていく
sign in画面は作らない

しかlog inできないようにする
2020-11-28 01:52:38 Google loginだと無理みたい
仕方ないので一時的にsign in画面を作っておこう
いや別にmail loginでもいいか
どうしようかな

login
pros
securityはこっちのほうが上?
cons
アカウントを再作成する羽目になったときが面倒
アカウント作成画面を復活させないといけない
いや普段は非表示にしておいて、アカウントを作成したいときだけbuildし直せばいいのか
それでも面倒だが

login
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にする

だけ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しちゃうから別に構わない?
これ読んでから考えるか。
なんでだ?
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するcomponent AuthGuard
を作る
認証されていたら、 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 力技で実装した
AuthGuard.tsximport * 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) {
const [user, initialising, authError]: [
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
追加して試してみる

21:26:55 成功
やっぱりdomainを追加していないだけだった
さっき時間かかっていたのは、codesandbox.ioを経由していたから?
逆につながっていたのが気になる
21:34:40 firestoreを開けない
これなら動く
ruleservice 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=**}
にした
22:45:32 /users/{userId}
の下にfieldsとしてuser情報入れられるじゃん!

側の接続方法
tsconst [values, loading, error] = useCollectionData<UserData>(
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: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 だんだん開発への意欲が上がってきたぞ

一定のしきい値を越えて、開発しやすくなった気がする
少しの修正を積み重ねてどんどん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 できた
何故か書き込みエラーが生じるのでなんでかと思ったが、引数を間違えていただけだった

const { setRecord } = useUpdator(userId);

const { 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

で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してみる