React + Firebase Note App Tutorial(Wes's) dev log
Did React + Firebase Note App Tutorial
why
jQとFirebaseで家計簿アプリ作ってたらG-stylussの逆求人もくもくのイベントで
話した学生全員にReact使って書けって言われた
cakeのledgerで改善点探してたらだいたいUIだったからメンテしやすいらしいReactというFront End Frame workを試してみる.
ledgerはdeployできてないので,簡単にできると聞いたfirebaseと相性良さそうなreactを選択,
とりあえずはYoutube Premirem契約してるし,この動画の通りにやって,自分の言葉で解説をかく学習方法を取ってみる.
しかし現在は推奨されていないRDBを使っていたので,Firestoreで置き換えて使用しようと思う

教材としてはYoutubeでwesのTutorialをベースとして使ったが
databaseからfirestoreに変えるためにDjamWareを参考にした.
環境構築
まずはnpmで汎用ツールをぶち込む
npm i -g create-react-app create-react-app reactnotes
結構時間がかかる
cd reactnotes npm start
でテスト,browserで動けばOK
あとnpm -i --save firebaseでbackendのfirebase 入れる

作成されたここのsrc/にYoutubeの通りにjsxを書いていく.
code
Component
React Component の記事に移設
props
Noteのconstructorに直接propsとして値を埋め込む方法がある.
super(props);
this.message = 'Hello from Note Component';
this.noteContent = props.noteContent;
this.noteId = props.noteId;
super(props)の下にnoteContent, noteIdを定義してpropsの下から this(このclass?)のに付け足す.
CSS, className
render(props) {
return(
<div className = 'note fade-in'>
<p className="noteContent">
{this.noteContent}
</p>
</div>
)
}
ReactではhtmlのclassはclassNameと書く必要がある.
render配下にさっき作ったthis.noteContentをうめ込む. これでNote Componentの中のnoteContentがrenderされる
propTypes
importしてあるProp Typesで型のチェックができる
Note.propTypes = {
noteContent: PropTypes.string
}
これで中身がstringでなければ警告が出る
参考
qiita.com
propsはコンポーネント作成時に値を指定することでコンポーネントで表示させたいデータを指定できます。 React.jsでコンポーネントを定義する時に、PropTypesを指定することでpropsにおける引数の入力チェックを行えます。 数値や文字列、配列などのバリデーションを行いたい時に便利です。
add App.css, No Static img,
ここのCSSをpaste.
使用する'./Static/img/2.jpeg'をpaste....
なかったの
代わりにpexelsのこれを使わせてもらいStatic/img/作ってぶち込んだ
https://www.pexels.com/photo/when-will-you-return-signage-1749057/
すると見栄えがよくなった

add Google Font
Parmanent Markerを選択

Latoも選択してimportのlinkをもらう

そして,index.cssを全て消してこのimportだけに書き換える! これで筆っぽいheadingは表示される.
add Note.css
お次はNote.cssをpaste....
完璧に組まれた!!!

次にFirebaseの処理に移る.
Back End
config.js
Firebaseを使う.configはsrc/Config/config.jsに記述.
ここにFirebase Console のsettingからadd web app, Configでcopyする

これを先ほど作ったconfig.jsにpasteして,
const firebaseConfig = {
apiKey: "hoge";
......
};
のconst firebaseCofig = をexport const DB_CONFIG =に書き換える
これでimportしたDB_CONFIGをAppで
import { DB_CONFIG } from './Config/config';
して使用する.
firebase/app
import firebase from 'firebase/app';
そしてfirebaseもimportするが,これだけでは
index.js:1 ./src/App.js Module not found: Can't resolve 'firebase/app' in '/Users/kaede/code/note-app/reactnotes/src'
のerrが出てしまう.
Youtuberのgithubのpackage.jsonとくらべたらfirebaseをnpm i --saveしてなかった!
したら
Line 5:10: 'DB_CONFIG' is defined but never used no-unused-vars Line 6:8: 'firebase' is defined but never used no-unused-vars
の「DBもfirebaseのAPI?も使われてないよ〜」のwarnしか出なくなった!!
Appのconstructorのナカに
this.app = firebase.initializeApp(DB_CONFIG);
this.db = this.app.database().ref().child('notes');
DB_CONFIGの内容でappをinitして,そこからDBのnotesテーブルを読みこむ.
component mount
ここが一番わからない
componentWillMount() {
const previousNotes = this.state.notes;
}
componentWillMountのナカにaddNoteに入れていたprev notesを入れる
this.database.on('child_added', snap => {
previousNotes.push({
id: snap.key,
noteContent: snap.val().noteContent,
});
this.setState({
notes: previousNotes,
})
})
さらにdbに子供が追加された時に,一時変数snapをとって
event drivenで関数を実行する処理を書く.
idには引数snapのkey, contentには引数の中身のnoteContentを
jQでよく使うvalを使って入れて,prevNotesにpushする.
そしてprevNotesをstateのnotesに組み込む.
変数prevNotesを噛ませるのは最初は冗長に見えたが,こうして処理が複雑に
なってくるとstateへ渡す処理をこうして分離するのは合理的に見えてくる.
duplicate err
なおこの段階でimport firebaseをした時に,
Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
とimportがduplicateしてる?エラーが出る
別の記事にまとめた.configl fileのやり方が変わっていたらしい.
その上でjQのアプリの時のようにfirestoreの解決策を取ろう.
add note
さっきのcomponentWillMountの処理で既存のaddNoteの処理は空になったので
そこに書き足していく.
databaseがあった時のやり方だと.onでevent drivenができたが,firestoreには
.onが存在しないのでerrになる.
db.pushもfirestoreではerrになる....
collection
なので
let db = firebase.firestore();
db.collection("notes").get(). then(function(querySnapshot) {
のようにfirestoreの解決策を取る.
なお.get()までではcllで出しても
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: t
promiseが帰ってきてしまう.
thenでsnapShotを返しても
lm rm ...
とかが帰ってくるので
this.db.collection('notes').get()
.then(snapShot => {
snapShot.forEach(doc => {
console.log(doc.data(),doc.id);
});
});
ここまでやらないとtableの配列は入手できない!!!
tableからgetして,thenでpromiseをforeachした後にdoc.data()までだす!
ここにcollectionを用いたcrudが書いてあった。 ここを参考にすると下記のようになる
Appのconstructor内に
this.ref = firebase.firestore().collection('notes');
this.unsubscribe = null;
this.state = {notes: []};
を書きnotesのpromse?をrefに入れる.
unsubscribeは不明.更新用の場所?
そしてstateに空のnotes配列を作成
constructorの後に
onCollectionUpdate = (querySnapShot) => {
const notes = [];
querysnapShot.forEach(doc =>{
const noteContent = doc.data();
notes.push({noteContent: doc})
});
this.setState({notes});
}
というupdateの関数を作り,querySnapshotを引数にとり,
componentDidMount() { this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate); }
componentDidMountでthis.unsubscribeにさっきのrefがsnapShotされる時?をとる。そこで onCollectionUpdateでは boards配列を作って、doc.data()を title, desc, auther, の変数に入れdocとkeyもいれ boardsにpushしてsetStateする。
このunsubからのonSnapshotでのonCollectionUpdateが,前回のon child addedを 代用しているように見える.
しかしsetStateのところで
Error: Objects are not valid as a React child (found: object with keys {id, noteContent}). If you meant to render a collection of children, use an array instead.
in h1 (at App.js:69)
in div (at App.js:64)
in div (at App.js:58)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
▶ 25 stack frames were collapsed.
App.onCollectionUpdate [as next]
src/App.js:28
25 | noteContent,
26 | });
27 | });
> 28 | this.setState({notes});
| ^ 29 | }
30 | componentWillMount() {
31 | this.unsubscribe = this.ref.onSnapshot(
のエラーが出る. このままでは使えないようだ
https://reactjs.org/docs/lists-and-keys.html
をみてさらにフォロワー様に教えてもらって直せた
react public document
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
として<li key = {hoge.toString() } >{ hoge } </li>
でtagのナカに入れて,stringにしてる.
また,mapの結果自体を変数に入れて,returnは変数一つにしている
とりあえず
this.state.notes.map(
(note) => {
return(
<h1>{note.key}</h1>
)
}
)
Line 70:19: Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>?
68 | return(
69 | <h1 key= {note.key}>{note.key}</h1>
> 70 | <h2 key= {note.key}>{note.noteContent}</h2>
| ^
71 | )
72 | }
73 | )
タグを複数返すとエラーが出た.
1つのコンポーネントが返せる Element の親は1つだけです
— やぎちゃん (@ygkn35034) April 25, 2020
複数返したいときは
<>
<Hoge />
<Fuga />
</>
のように <> </> (React.Framentの糖衣構文)で囲ってあげなきゃいけません
だそうだ,returnするもの自体にdivで括るsyntax sugarらしい
これで出せたが,console.logしてみるとnoteContentのナカにnoteContentが入った
{key: "notes", doc: n, noteContent: {…}}
key: "notes"
doc: n {lm: t, um: t, Rm: n, Am: false, Pm: false, …}
noteContent: {id: 1, noteContent: "hoge"}
__proto__: Object
入れる際の階層構造を正した
querySnapshot.forEach(doc =>{
const noteContent = doc.data().noteContent;
notes.push({
id: doc.id,
doc,
noteContent,
});
});
ただこれでもidがnotesになってるから,idにもdoc.data()のさらに下の
doc.data().idを入れる.(実装としておかしいか?)
これでとりあえず,firestoreの値をReactのstateに入れてcllすることができた.
ここでもkeyがないerrが出るが,return で返しているのはdivなので,divに入れたら 解決した
correct code (render)
{
this.state.notes.map(
(note) => {
console.log(note);
return(
<div key= {note.id.toString()}>
<h1>
{note.id},
{note.noteContent}
</h1>
</div>
)
}
)
}

ようやくfirestoreからhtmlに出せた.
なおhtmlのclassを壊してるからレイアウトがない
Youtubeでは
またid順に読み込まれたりはしていないからfilterも必要
componentWillMountで書いていると警告がでた
react-dom.development.js:88 Warning: componentWillMount has been renamed, and is not recommended for use. See https://fb.me/react-unsafe-component-lifecycles for details. * Move code with side effects to componentDidMount, and set initial state in the constructor. * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder. Please update the following components: App
not recommendだそうだ.下記のリンクを見てみると
Unsafeになってるから
npx react-codemod rename-unsafe-lifecycles
でrenameできるらしい.だが新しいのどう使うか書いてないし,全然わからん放置.
まぁ最終的にはhookで書き直すからヨシ!
return(
<div key= {note.id.toString()}>
<Note noteContent=
{note.noteContent}
noteId={note.id}
key={note.id}
/>
</div>
)
Noteにkeyを渡す方式でいく
無事にfirestoreの値が綺麗に表示された!!


次は追加の処理をfirebase/databaseから ../firestoreに置き換える
ADD
constructorの作成
dJam
import firebase from '../Firebase';
...
class Create extends Component {
constructor() {
super();
this.ref = firebase.firestore().collection('boards');
this.state = {
title: '',
description: '',
author: ''
};
}
.........
djamではCreateとして作成している, propsは渡していない.
さらにstateにtitle, desc ,author,をカラで初期化.
Wes
class NoteForm extends Component {
constructor(props) {
super(props);
this.state = {
newNoteContent: 'New Note Content',
}
this.handleUserInput = this.handleUserInput.bind(this);
this.writeNote = this.writeNote.bind(this);
}
...
一方Wesのではpropsを渡して,newNoteContentのみを初期化し
handleとwriteをbindしている
propsを渡しているのはthis.props.addNoteを受け取るためか.
kaede
Appと同じようにここNoteFormでもthis.ref...でnotes tableから持ってきた.
writeNoteは置いておいて,AppのaddNoteだけ書き換える.
Wes
Wesのはrenderに
<input ... onChange = {this.handleUserInput} />
<btn ... onClick = {this.writeNote} >...
と指定ElementでにonChange, onClick時に関数を発火させているが
dJam
<input type="text" class="form-control" name="title" value={title} onChange={this.onChange} placeholder="Title" />
dJamではonHogeで直接処理を書き込んでいる.なのでbindする必要がないバウ.だからonChangeにthis.onChangeを入れている
dJam
handle input関数,onChange
dJam
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
stateの指定のものに入れるのではなく,stateの配列名を指定して, そこにtarget.valueを入れて,それをsetStateしている
Wes
handleUserInput(e) {
this.setState({
newNoteContent : e.target.value,
})
}
userが入れるところはnoteの中身だけなので,変数噛ませないで直接 this.setStateで入れるところを指定して入れている.
よく見るとonChange(e)でとる方が再利用せいが高い.
writeNote, onSubmit
Wes
writeNote(){
this.props.addNote(this.state.newNoteContent);
this.setState({
newNoteContent: '', // empty input box
})
}
.........
addNote(note) {
this.db.push().set(
{ noteContent: note }
)
}
writeNoteではaddNoteに値を渡すのと,inputをemptyにするだけの処理になっている
dJam
onSubmit = (e) => {
e.preventDefault();
const { title, description, author } = this.state;
this.ref.add({
title,
description,
author
}).then((docRef) => {
this.setState({
title: '',
description: '',
author: ''
});
this.props.history.push("/")
})
.catch((error) => {
console.error("Error adding document: ", error);
});
}
一方dJamではstateの各値をtitle, desc, author,に分割代入し,
this.refに全て追加してhistoryに/を追加している,謎
またerr字の処理も書いている
render() {
const { title, description, author } = this.state;
renderでも同様にしてstateから取り出している
kaede
wesの作品ではnoteにはnoteContentのdataしかなくて,idはarrの大きさ+1にした

DBへの追加完了!!
UX的にはGoogle DocsやkeepみたいにonChangeでDB自体の変更もやってみると 面白いかもしれない
またaddNoteでpush処理をしていて,NoteFormはaddNoteにnewNoteContentを 渡すだけなので,NoteFormにはthis.refは必要がなかった.
ここでloginしたuserごとにpushできないと完成にならない...と頭を抱えたが,とりあえずはlogin無視で一つのtableでRead, Create, Update, Edit, Deleteできるようにする.
連番idとcreated_at
arrのlength + 1を追加する分のidとしていたら
idやキーを配列の長さで取ると削除機能実装時に死ぬ
と教えてもらって代わりにcreated_atをkeyとする方式をとった
App.js > render
<div className="notesBody">
{
this.state.notes.map(
(note) => {
console.log(note);
return(
<Note noteContent=
{note.noteContent}
key={note.created_at}
/>
)
}
)
}
</div>
でこれでstateのcreated_atをkeyとして渡して
Note.jsx > render
<p className="noteContent">
{this.noteContent}
</p>
これでnoteContentのみ表示する

firestoreで const created_at = new Date(); で送ると このようにtimestampで保存される
delete
ШесのYoutubeでは
this.database.on('child_removed', snap => {
for(var i=0; i < previousNotes.length; i++){
if(previousNotes[i].id === snap.key){
previousNotes.splice(i, 1);
}
}
と
removeNote(noteId){
console.log("from the parent: " + noteId);
this.database.child(noteId).remove();
}
と
<Note noteContent={note.noteContent}
noteId={note.id}
key={note.id}
removeNote ={this.removeNote}/>
になっている. removeNoteのbtnをcontentと共に出して
<span className="closebtn"
onClick={() => this.handleRemoveNote(this.noteId)}>
×
</span>
handleRemoveNote(id){
this.props.removeNote(id);
}
idを渡してremoveNoteする
database.on(child_removed...になっているので,dJamの テキストを参考に...わからん
firebase公式だと
db.collection("cities").doc("DC").delete().then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
docのidを渡せばいいことがわかる.
このためにnotesの中身だけじゃなくてdoc自体もrefに入れておく必要があったのか
idは
this.state.notes.map(
(note) => {
console.log(note.doc.id);
で
3YMpsV2NxfXWYWThpCl4 App.js:78 AS5v0YCUxqHX5cjeCeUR App.js:78 Kds0WUyMGROVwV55frEE App.js:78 zVFi3oudXwQG3vGXsVGa App.js:78 3YMpsV2NxfXWYWThpCl4 App.js:78 AS5v0YCUxqHX5cjeCeUR App.js:78 Kds0WUyMGROVwV55frEE App.js:78 zVFi3oudXwQG3vGXsVGa
consoleにidが出てくる.担っている担っている
states.notesをmapのあと
でNoteにstateのidを渡す
Noteのconstructorで受け取る
this.id = props.id;
×
TypeError: Cannot read property 'doc' of undefined
removeNote
src/App.js:62
59 | }
60 | removeNote(id) {
61 | console.log(id);
> 62 | this.ref.doc(id).delete.then(function() {
| ^ 63 | console.log('deleted');
64 | }).catch(function(error) {
65 | console.error('error!!!!',error);
View compiled
Note.handleRemoveNote
src/Note/Note.jsx:16
13 | this.handleRemoveNote = this.handleRemoveNote.bind(this);
14 | }
15 | handleRemoveNote(id) {
> 16 | this.props.removeNote(id);
| ^ 17 | console.log('remove',id);
18 | }
19 | render(props) {
View compiled
onClick
src/Note/Note.jsx:23
20 | return(
21 | <div className = 'note fade-in'>
22 | <span className='clsebtn'
> 23 | onClick={()=> this.handleRemoveNote(this.id)}
| ^ 24 | >
25 | ×
26 | </span>
わからない, idが渡せない