質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Material-UI

Material-UIは、Material Designを利用可能なオープンソースのReact向けUIコンポーネントキットです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

2回答

4421閲覧

JSのみで投稿サイトのビュー

MOTOMUR

総合スコア195

Material-UI

Material-UIは、Material Designを利用可能なオープンソースのReact向けUIコンポーネントキットです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2017/10/10 13:39

編集2017/11/24 00:55

javascriptとreact、material-uiを用いて、アプリ作成しています。

現在のプロジェクトはこちら。

テストサーバーはreact-scripts startにて。
ホスティングサーバーはfirebaseにて。

課題点(要素の多さによる遅延の軽減)

現在、componentDidMountにて、たくさんのfetchや処理が混雑していて、遅延によりページが表示できないことが増えてきました。

これを解消したいです。

どのように復活させられるでしょう?

<今後実装したいが搭載に困っている機能>
①多言語化(とりあえず英語と日本語)
②デザイニング

解決できるかたからのメッセージまたはGitへの直接の書き込み待ってます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

そもそもjs上だけで動くものを作りたい

jsxは使いたくないということですか?
jsでテンプレートを書きやすくするためにちょっとだけjsを拡張したものなので、すぐに学習できると思います。

jsxはreactのランチャーから行くと、たくさんのエラーを吐きました。

エラーメッセージを提示していただくと解決につながるかもしれません。

この辺りの機能の名前がわからなくて

  • ユーザー認証 -> authentication
  • ゲスト、ユーザーが見れるプラットホーム -> 普通のウェブアプリ?(わかりません)
  • ユーザー課金、ゲスト課金 -> payment / 決済

ちなみにnode_modulesは.gitignoreに入れたほうがいいですよ。

投稿2017/10/10 14:16

編集2017/10/10 14:54
karamarimo

総合スコア2551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MOTOMUR

2017/10/10 14:48

回答ありがとうございます!jsxに対して不信感がなくなり、勉強しようと思いました。そこで、方針を参考にしたブログのjsxの構文を使ってみることにしました。しかし、エラーを吐きます。このSyntaxエラーはエラー文ベタ塗りで検索かけてもよくわかりませんでした。
MOTOMUR

2017/10/10 14:52

追記:.gitignoreの件、アドバイスありがとうございました!node-moduleを除外し、コミットしました。間違いがあれば指摘してもらえると幸いです。
karamarimo

2017/10/10 14:52

syntax errorなら、どのファイルの何行目の何文字目、というのが表示されると思いますので、それを参考に修正できると思います。
MOTOMUR

2017/10/10 15:00

@の検索が、異常に難しく、解決が難しかったです。       ・@はjsx上の構文でしょうか? その場合、@を受け入れられるLintをインストールしなくてはいけないでしょうか?
MOTOMUR

2017/10/10 15:02

消したら、一応コンパイル通ったので、実行して見ます。通知をなんどもすいません。
MOTOMUR

2017/10/10 15:15

ブログにあったサンプルは、reduxも使用しているので、reduxとreact-reduxを追加しました。 import文のfrom react/redux はreact-reduxだと思って修正しましたが、致命的にたくさんのエラーを吐きギブアップ状態です。。エラーのうちの1例としては、怒られてなかったindex.jsの 「ReactDOM.render(<App />, document.getElementById('root'))」まで怒られる始末です。
karamarimo

2017/10/10 15:20

@はデコレーターというもので、jsの最新機能?です。それ自体はjsx特有のものではありません。 connectはreduxの関数であり、reactのcomponentをreduxのstoreとつなげるものです。 恥ずかしながらreduxについてはほとんど知らないのでお答えしにくいですが、デコレーターはbabelのconfigをちゃんとすればsyntax errorにならないはずです。 この記事がconnectを説明してます。 https://mae.chab.in/archives/2885
karamarimo

2017/10/10 15:22

reduxは無理して使わなくてもいいと思います。あくまでappどこからでもアクセスできるデータのシステムを提供するものです。
MOTOMUR

2017/10/10 15:28

今後、必要かどうかは置いておいて、Reduxを勉強しようと思います。 しかし、今回のアプリ作成の目的は、react.jsとmaterial-uiの最小単位で、早く完成させることなので、とりあえずアドバイス通りReduxは放置します。さて、ここで新たに問題なんですが、Reduxのconnectを外した場合、そこに変わるコンポーネントが必要なんですが、全くどう変えていいか、見当もつきません。 代わりとなるものはすぐに作れるものでしょうか?
karamarimo

2017/10/10 15:39

そこに変わるコンポーネントとはどういうことでしょうか? class UserOnly自体コンポーネントなのでそれを残して、 @connect(state => ({ session: state.session })) を削除すればいいのではないでしょうか。
MOTOMUR

2017/10/10 15:44

バカで申し訳ないです!消して実行しましたが、Reduxの爪痕でエラーがたくさんあるので、修正して試して見ます。
MOTOMUR

2017/10/10 16:06

./src/components/GuestOnly.jsx Line 5: React.PropTypes is deprecated since React 15.5.0, use the npm module prop-types instead react/no-deprecated とエラーになってしまいます。
karamarimo

2017/10/10 16:16

React.PropTypesがReact 15.5.0から廃止になり、prop-typesという別ライブラリになったということです。使用するにはnpmでインストールする必要があります。 PropTypesは文字通りコンポーネントのpropのデータ型をチェックしてくれる、開発用の機能です。 なのでそれがいらないなら使う必要はありません。 React.PropTypesからprop-typesへの移行の仕方 https://reactjs.org/blog/2017/04/07/react-v15.5.0.html#migrating-from-reactproptypes
MOTOMUR

2017/10/11 02:24

ありがとうございます!PropTypesは修正できました! ターミナル上ではコンパイルできるんですが、ランチャー上で怒られます。 エラーを本文に追記します。
karamarimo

2017/10/11 02:29

すみませんがランチャーとは何のことでしょうか...。"react-scripts start"のことですか?
MOTOMUR

2017/10/11 02:35

それです!ごめんなさい!ランチャーって覚えちゃってました。修正します。
karamarimo

2017/10/11 02:49

エラーですが、 TypeError: Cannot call a class as a function UserOnly とあるので、たぶんUserOnlyクラスを"UserOnly()"みたいに関数として使ってしまったのが原因ではないでしょうか。
MOTOMUR

2017/10/12 02:31

ユーザー認証はFirebaseでやってみることにしました。現在git commitで苦戦中
karamarimo

2017/10/12 05:05

precommitとかjestは全然知らないのですが、 collectCoverageFrom とかは jest のコンフィグオプションらしいですがそれがpackage.jsonで変なところに入ってるのではないでしょうか。
MOTOMUR

2017/10/12 05:21

解決しました。ありがとうございます。package.json のprecommitが悪さしていたようです。 Firebase導入に苦戦しているのですが、material-uiのテキストから受け取った情報を変数(?)に入れて、 firebase.auth().signInWithEmailAndPassword(email, password) このメールとパスワードに渡したいんですが、やり方がわかりません。。。。
karamarimo

2017/10/12 06:01

material-uiは使ったことがないですが、reactのコンポーネントのライブラリなんですかね。 普通のinput要素と同様に、Controlled componentにすれば、テキストボックスに入ってるテキストとコンポーネントの変数の間で同期?できます。 https://reactjs.org/docs/forms.html#controlled-components TextFieldコンポーネントを使っていますか? http://www.material-ui.com/#/components/text-field このページのControlled exampleのとことか参考になるのではないでしょうか。右にある"<>"をクリックすればソースが見れますよ。
MOTOMUR

2017/10/12 08:18

ありがとうございます。使い方は何と無くわかりました。http://www.material-ui.com/#/components/text-fieldのControlled exampleの constructor(props) { super(props); や this.state = { value: 'Property Value', }; } とか、なんのために使ってて、なにをしてるのかわからなくて困ってます。
MOTOMUR

2017/10/12 08:19

詳しく教えてくれてるサイトとかも見つけられないので。お願いします。
karamarimo

2017/10/12 08:54

react componentの概念は理解なさっていますか? componentにはpropsとstateがあります。 公式 https://reactjs.org/docs/state-and-lifecycle.html https://reactjs.org/docs/components-and-props.html 日本語解説 https://qiita.com/kuniken/items/a22adda392ccc30011b1 https://qiita.com/kyrieleison/items/78b3295ff3f37969ab50 constructorはclassのコンストラクタ(そのまま)です。 インスタンスを作成するときに呼び出されます。 C#とかJavaでもコンストラクタ内でsuper(上位クラス)のコンストラクタを呼び出す、ということはよくあると思いますが。 日本語解説 http://www.yunabe.jp/docs/javascript_class_es6.html
MOTOMUR

2017/10/12 13:59

state props勉強してきました。不明点は質問文を編集しました。お力添えをお願いします。
karamarimo

2017/10/12 14:28

firebase.auth()... をクラスの外にそのまま書いてますが、これだとこのjsファイルが読み込まれた時点で実行されてしまうのでまずいのではないですか?
MOTOMUR

2017/10/12 14:31

自分のイメージだと、class内に入ってるように感じるんですけど、もしかしてクラス内の判定はrender()の中!?!?
MOTOMUR

2017/10/12 14:33

中にしたら怒られませんでした。 emailとpasswordがnot definedみたいです。どこかで初期設定必要なんですかね。
karamarimo

2017/10/12 14:35

今気づきましたが"}"が最後欠けてませんか?
karamarimo

2017/10/12 14:36

最後というか、一番外側の{}のペアが、}が欠けてると思うのですが。
MOTOMUR

2017/10/12 14:37

現在のコードに編集しました。
MOTOMUR

2017/10/12 14:40

現在の怒られることは、 emailとpasswordがnot defined
karamarimo

2017/10/12 14:46

すみません言うのが遅れましたがrender()の中に書くのはおかしいというか、使い方がよくありません。 emailとpasswordはユーザーに入力させるんですよね? いつ認証(firebase.auth()....)させたいんですか?
karamarimo

2017/10/12 14:48

ちなみに、そのコンポーネントのstateにアクセスするには this.state.<変数名> です。render関数内でも使われていますよね?
MOTOMUR

2017/10/12 14:49

入力後に、ログインボタンを押したタイミングでですね。 ログインボタン追加してきます。 ログインボタンの入力判定も handleChange = (event) => { this.setState({ ??? }); }; みたいな感じでできますかね_?
MOTOMUR

2017/10/12 14:51

とりあえずボタンだけ追加しました。
MOTOMUR

2017/10/12 14:58

さらに修正
karamarimo

2017/10/12 14:59

ボタンの入力も同様にできます。setStateする必要があるかは知りませんが。 handleChangeがどこにも使われていないので意味がないですね...。 もう一度↓のControlled exampleのソースを見て、コンポーネントの変数とTextFieldの値が同期されるようにしてみてください。重要なのはvalueとonChangeをpropsで渡しているところです。 http://www.material-ui.com/#/components/text-field イベントハンドラー(今で言うhandleChange)は別々に作ったほうがいいと思います。
MOTOMUR

2017/10/12 15:01

現在エラー:not defined  email password
MOTOMUR

2017/10/12 15:07

handleChange別に作ったり、色々変更しました。
MOTOMUR

2017/10/12 15:24

コンパイルできるようになりましたが、firebaseのところと、index.jsのReactDOM.render(<App />, document.getElementById('root'))がダメと、react-scripts start 上にてダメになります。
karamarimo

2017/10/12 15:27

細かい気になる点がいろいろあるので、Demoを作りました。 https://codesandbox.io/s/2vmwym63r (ボタンクリック時に認証処理の代わりに、単にemailとpasswordをconsole.logしています) TextFieldに password={this.state.password} など属性を指定していますが、これは意味がありません。TextFieldの値は"value"によって渡す、と公式ページに書いてあります。 また2つのTextFieldに同じidを与えていますが当然HTMLとしてよくありません。 最後に、RaisedButtonの onClick に onClick={firebase.auth().signInWithEmailAndPassword(email, password)} と直接やってほしいことを書いていますが、これは間違っています。 ここで渡すべきは関数です。関数を渡せば、クリック時にその関数が実行されます。 TextFiledと同様に一つイベントハンドラーの関数を作るといいです。
MOTOMUR

2017/10/12 15:35

ありがとうございます。アドバイス通り修正しました。このエラーが謎です。Syntax error: Unexpected token, expected , (31:14) 29 | handleChange_button = (event) => { 30 | this.setState({ > 31 | firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password) | ^ 32 | }); 33 | }; 34 |
karamarimo

2017/10/12 15:42

jsのsyntaxはあまりご存知ないのでしょうか? { key: value } はオブジェクトリテラルというもので、このsyntaxに合ってないということです。 そもそも今 setState する必要はあるのでしょうか? setState は state を更新する関数ですが、理解なさっていますか?
MOTOMUR

2017/10/12 15:56

あまり詳しくないです。しかし、このsetStateに関してはコピー後の消し忘れです。申し訳ありません。無事コンパイルでき、onClickまでは動作するようになりました。
MOTOMUR

2017/10/12 16:00

度々の稚拙な質問にお答えくださり、感謝しかありません。ご容赦を。
karamarimo

2017/10/12 16:10

いえいえ。Nodejsでいろんなライブラリを使って開発するのは最初は誰でも混乱すると思います。 ただ、こういったwebアプリの開発はjsやhtmlの基礎をしっかり勉強してから始められた方が、つまづきにくいかと思います...。
MOTOMUR

2017/10/13 04:22

ルーティングに困っています。Loginをクリック後に、ファイアーベース起動、ログイン、ログイン状態をuserに落とし、それで条件分岐しています。react-routerのrender外での指定パスに飛ばす方法が調べてもいまいちわかりません。。。。
karamarimo

2017/10/13 04:45

「react-routerのrender外での指定パス」というのは何のことでしょうか? App.js で <Switch> 内に <Route>がいくつかありますが、これの一つに飛ぶということですか? 以下細かい点 - event.target.email/password ではなく event.target.value です。 - firebase.auth().onAuthStateChanged をクリックするたびに実行するのはよくありません。あくまでこれはイベントハンドラを設定する関数なので、コンポーネントが表示されるときに一回実行すればいいと思います。 ライフサイクルメソッドの componentWillMount() か componentDidMount() を使えばできます。 https://reactjs.org/docs/react-component.html#componentwillmount
MOTOMUR

2017/10/13 13:03

<Route>のうちの一つに飛ばしたいということです。 componentWillMount() と componentDidMount()  を自分なりに解釈し、コード訂正しました。間違いあれば教えてください。
MOTOMUR

2017/10/13 13:51

demoのログインボタンを押してもHelloページに飛べませんでした泣 historyの説明を読んでいますが、解読中。
karamarimo

2017/10/13 13:55

あっすみません。Login.jsをセーブし忘れました。 更新したのでもう一度ご覧ください。
MOTOMUR

2017/10/13 14:08

Routehistryの使い方。理解できました。コード追加しました。console.logで確認したところ、componentDidMount()に到達していないようです。componentDidMount()はボタン押したら反応するのは認識間違いでしょうか?
MOTOMUR

2017/10/13 14:11

それともonButtonClick内に条件分岐等全部入れてしまっていいのですかね?
karamarimo

2017/10/13 14:18

あ、componentDidMountでしたね。間違えました。 そっちはマウントされた直後に呼び出されます。
karamarimo

2017/10/13 14:22

先述の通り firebase.auth().onAuthStateChanged はイベントハンドラを設定する関数です。なのでコンポーネントが作成されるときに一回やればいいということです。
MOTOMUR

2017/10/13 14:30

ログインボタンを押して、ログイン完了した時にはホームに、できなかったらログインページにとしたいのですが。この場合はボタンを押されたタイミングに、ログイン状態をfirebase.auth().onAuthStateChangedで取得して、条件分岐するっていう処理と。ページが開かれて、コンポーネントが作成される直前にログイン状態を確認して、ログイン状態なら、ログインページに来た瞬間にホームに飛ばすっていう二つの処理が必要って感じですかね?
MOTOMUR

2017/10/13 14:34

コードをボタン押した時の処理だけ追加しました。ページ遷移直後は未実装。エラー内容はログインページへ飛ばすpushのprops
karamarimo

2017/10/13 14:54

2つの処理が必要という意味ではありません。 firebase.auth().onAuthStateChanged(f) は使い方でいうと btn.addEventListener("click", funtion () { ... }); と同じです。ロードされた時に、最初に1回だけやればいいのです。 非同期処理というのをご理解されていないような気がします。 ボタンを押した時に firebase.auth().signInWithEmailAndPassword( ... ); を実行しますね?その後、認証が完了するのはいつですか? この行が実行された直後ですか?いいえ違います。 いつ完了するか分かりません、というのが答えです。 なのでイベントハンドラーを設定するのです。 コンポーネントが作成されたときに firebase.auth().onAuthStateChanged(f) としておけば、認証が完了したときに f が実行されます。
MOTOMUR

2017/10/13 15:04

なるほど!!!!やっと理解しました。ありがとうございます!自分のコードを修正しました。修正したところ、TypeError: Cannot read property 'history' of undefinedとなるのですが、historyはインポートしないと使えなかったり、どこかで定義しなければならないのでしょうか?import history〜 とするとhistoryどこにも使ってないと言われるし。調べてもよくわかりません。
karamarimo

2017/10/13 15:07

firebase.auth().onAuthStateChanged(function(user){ if(user){ this.props.history.push('/') } else { this.props.history.push('/login') } }) の props が undefined になる理由ですが、一般に関数の中に入ると"this"が指すものは外側での"this"と異なります。 なので関数の外側で const self = this; と退避して、関数の内側で self.props.history... とすればうまくいくと思います。 this についても調べておくとよいかと思います。
MOTOMUR

2017/10/13 15:25

うまく動くようになりましたありがとうございます。まだFirebaseのログアウトを実装しておらず、ログインしてない時にログインのビューに飛べるかどうかわからなくなってしまったのですが、確認していただけますでしょうか。gitに現在のコードをのせました。ログイン用のデモアカウントはID admin@admin.com PW adminn です。
MOTOMUR

2017/10/13 15:29

確認できました。変なこと頼んで申し訳ないです。
MOTOMUR

2017/10/13 15:30

簡易ログアウトボタン実装し他コードをgitしておきます
karamarimo

2017/10/13 15:30

パスワードとかここに載せないほうがいいと思いますが...。
MOTOMUR

2017/10/13 15:32

確認用IDpsですので。管理できているので大丈夫です。すいません笑
MOTOMUR

2017/10/13 15:45

ヘッダーのログインボタンとログアウトボタンの管理についてどうやっていいかわかりません。。
MOTOMUR

2017/10/13 15:47

後、これは余力があればやりたいのですが、ログインページでヘッダーのログインボタンを消す方法もわかると嬉しいです。。
karamarimo

2017/10/13 16:11

「ヘッダーのログインボタンとログアウトボタンの管理」 管理というと具体的に何でしょうか?
MOTOMUR

2017/10/13 16:13

質問でも更新しているのですが、ログイン中はログアウトボタンを表示して、ログインしていない状態だとログインボタン。ログインページではログインボタンもログアウトボタンも表示しない。という管理をしたいです。
MOTOMUR

2017/10/14 03:05

reduxあった方が楽そうですね。redux導入を先にします。
MOTOMUR

2017/10/14 03:22

うーん難しいRedux。
karamarimo

2017/10/14 06:29

私はいつも公式ページを見るのが一番いいと思ってるいるのですが、英語だと理解しにくいというならこの記事とか図がいっぱいあっていいんじゃないですかね。 https://qiita.com/kiita312/items/49a1f03445b19cf407b7 firebaseについてですが、ここを見ると https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user いつでも firebase.auth().currentUser で現在のユーザーを取得できるそうです。 なので、もし(ログインページ以外で)ログイン状態が変化したときにreactに自動的に表示を更新してほしい、というのでなければ redux は必要ないかもしれません。
MOTOMUR

2017/10/14 13:44

firebaseベースでログイン状態管理することにしました。不明なのはcomponentWillountをコンポーネント作成前に走らせて、userを定義して、render内で使っているつもりなのに、render内のuserがnot definedなことです。
MOTOMUR

2017/10/14 13:48

コードは質問本文に掲載。
karamarimo

2017/10/14 15:30

initializeAppは一番の親であるApp.jsでやるだけでいいと思います。 SideNavのcomponentWillMountでもやってしまうと、2度初期化することになります。 userはcomponentWillount関数内で定義された変数なのでその中でしかつかえません。 これはjs(というかたいていの言語)の基本です。 stateにuserを入れましょう。
MOTOMUR

2017/10/14 15:50

コンパイル可能になりました。initializeAppはAppに戻しました。stateにuserを追加しました。しかし現在、ログイン状態でも、そうじゃなくても、常に右上がログイン状態です。コンポーネントのリロードが必要なのでしょうか。それとも二項演算子の書き方がまずいでしょうか。
karamarimo

2017/10/14 16:44

userはcomponentWillountで取得するので、その時点でログインできていなければnullになります。 userはちゃんと取得できていますか?
MOTOMUR

2017/10/15 08:42

ボタン制御できました。gitにあげました!ログインページで消す条件は思いつかないので放置してます。
MOTOMUR

2017/10/15 12:42

ログインページでは消せました。ありがとうございます。次はサインアップページつくろうと思います。
MOTOMUR

2017/10/16 02:34

サインアップのページを簡単に追加しました。アドレスの確認を組み込んでないので適当なアドレスでも登録できるのですが、とりあえず放置で。次に投稿型のホームのビューを作りたいです。 Userの入力によってできた記事を一個づつ追加する感じにしたいのですが、どうやったらできるでしょう
karamarimo

2017/10/16 02:58

ユーザーがテキストを入力して投稿するとfirebaseのデータベースに追加され、またユーザーごとに過去の投稿が一覧で見れるようにするということでしょうか。 具体的な設計がよく分かってないので抽象的にしか言えないですが、投稿ボタンを押したら firebase のデータベースに送るのはまあできると思います。あとはデータベースの変更をリッスンして、変更されたら state を適当に更新すればいいと思います。 firebaseのデータベースの使い方はここを見れば分かるでしょう。 https://firebase.google.com/docs/database/web/read-and-write?hl=ja
karamarimo

2017/10/16 03:20

言い忘れてましたが、onAuthStateChangeのリスナーを設定するcomponentは、unmount(ページから削除される)の時にunsubscribe(リスナーを外す)する必要があると思います。そうしないと、その component を表示するたびにリスナーがどんどん増えてしまいます。 componentWillUnmount() https://reactjs.org/docs/react-component.html#componentwillunmount https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged この返り値が unsubscribe する関数そのものなので、これを state に保存しておいて、componentWillUnmountで呼び出せばよいでしょう。 componentWillMount() { var unsub = firebase.auth().onAuthStateChanged(function(user) { // ... }); this.setState({ unsub }); } componentWillUnmount() { this.state.unsub(); }
MOTOMUR

2017/10/16 14:41

そうですね。まず作る段階に入るために欲しい機能は、ログインユーザーが、mypageを作るボタンを押したら、'./createMyPage'に飛ばして。そのページでタイトル、説明欄を入力し、mypage作成を押すと'./UserName/Top'のページを作り、そのユーザーが記入した内容を登録されるっていう機能です。ここで入力されて登録した内容は、時系列表示じゃなくて、固定表示で出したいです。とりあえず、'./createMyPage'を作り、そこへのリンクを作り、mypage作成画面までは自分で作れると思うので、作成していこうと思うのですが、'./UserName/Top'のページを作り、そのユーザーが記入した内容を登録するという機能を考えていたのですが、投稿内容を値としてデーターベースに送ればいいってことまではわかったんですが、その後の昨日作成に詰まりそうなのですが、受け取ったデータをもとに新規作成するという機能はどうやって実現できますか?教えてください。
karamarimo

2017/10/16 15:55

受け取ったデータをもとに新規作成するというのは、データベースから受け取ったユーザーデータを元にページを表示するということですか? この記事(とPart2も)が参考になると思います。 https://css-tricks.com/intro-firebase-react/#article-header-id-10 要はイベントハンドラーを設定して、データベースに変更があったら、state を更新するってことです。
MOTOMUR

2017/10/17 05:24

makemypageを作成し、入力されたデータをデーターベースに記録するところまで作成しました。今後、makeMyPageは1ユーザー1ページのみとしたいのでその処理も書きたかったのですが、とりあえず放置です。ホームにその値を参照し、表示させるという機能も必要なので実装します。 それと並行して、./userpage/:username(これは本人以外のビュー。コメント機能もおいおい付けたい。)と、./mypage/:username(これは本人のビュー。新規投稿等機能つき。新規投稿はユーザーページに時系列でマイルされるようにしたい。) を作りたいのですが、とりあえず、ビューの作成にあたり、そのリンク元の行き先を、<Route path='./mypage/:username'>としたいのですが、どうやってパスにusernameを渡したらよいのかわかりません。あとはビューの内容は、usernameをもとにデーターベースからデータを取ってくるようにすれば良いですよね? 不明点教えてください。
MOTOMUR

2017/10/17 05:25

makeMyPageも含めたコードはgithubにあげてあります。
karamarimo

2017/10/17 05:45

this.props.history.push('/mypage/' + this.state.username) とすれば、動的にマイページに飛べると思いますが、 /mypage/:username ではなく /mypage でもいいのでは? ユーザー名は firebase から取得できるので。 > usernameをもとにデーターベースからデータを取ってくるようにすれば良いですよね? firebaseについてはよく知らないのですが、たぶんそうなんじゃないでしょうか。
MOTOMUR

2017/10/17 07:08

質問文更新しました。データーベースの内容を参照するところまではかけたと思うのですが、その後、個別の値をstateに入れて、renderで表示するという一連の流れができません。教えてください
karamarimo

2017/10/17 07:50

firebaseについてはよく知らないので、公式ガイドを見ながら書いてますが、まずデータベースをどういう形にするのか決めたほうがよいと思います。 公式ガイド https://firebase.google.com/docs/database/web/structure-data なるべく階層が深くならないようにしたほうがいいと書いてあります。 makeMyPageは、自分のページを作るだけですよね? データを取得して表示するのは別のページじゃないでしょうか。 データを取得する方法は、先程の記事に例が書いてあります。 itemsRef.on('value', (snapshot) => { console.log(snapshot.val()); }); .on で ref の value イベントにリスナーを設定すると、.on を実行した直後に1回呼び出され、その後 ref の内容が変化するたびに呼び出されます。 https://firebase.google.com/docs/reference/js/firebase.database.Reference#on これを使って state を データベースにあるユーザーデータと同期させればいいと思います。
MOTOMUR

2017/10/17 12:58

ありがとうございます。やってみます。ところで、質問文のコードでしたが、中身を別のページからきりはりするときに、コンポーネントの名前を切り取り元のそのままにしてしまったようです。先述のコードも現在のコードも、名前以外はそのままですが、mypageのものです。なので、データを取得し、表示するコードであってます。
MOTOMUR

2017/10/20 01:28

firebase.database().ref.once(this.state.userId).then(function(dataSnapshot) { //handle read data }); を使って、データを取得し、表示しようと思います。 dataSnapshot.val() がデータベースからの出力でしょうか? どうやってstateに代入したらよろしいでしょうか。
MOTOMUR

2017/10/20 02:01

const self = this firebase.database().ref().once('value').then(function(snapshot) { self.setState({ username:snapshot.val().user, explain:snapshot.val().explain, title:snapshot.val().title }) }); }; これで多分、stateに入れられてます。このあと表示させたいのですが、 render() { return ( <div> <div className='col s12 m4'> <Card> <CardTitle title='Mypage for {username}' subtitle='{this.state.title}'/> <CardText> {this.state.explain} </CardText> </Card> </div> </div> )} } こうすると変に表示されてしまいます。基礎のような気もしますが、解決案が見つかりませんでした。
karamarimo

2017/10/20 04:05

> dataSnapshot.val() がデータベースからの出力でしょうか? そうです。どうやってusernameなどを取り出すかは、完全にそのデータベースの形式次第です。 console.log(snapshot.val()) をして、どのような形式か確認してみてはどうでしょうか。 現在ログインしているユーザーのデータだけ取得できればいいんですよね? firebase.database().ref().once.... とすると、データベースまるごと取得することになります。 .ref の引数にパスを指定して、現在のユーザーのデータだけを取得するようにしたほうがいいと思います。 あと、{username} ではなく {this.state.username} ですよ。 最後に、snapshot.val() がどれだけ重い処理かわからないですが、何回も呼び出すのは無駄に処理が増えるので、返り値を変数に入れるなどして避けたほうがいいと思います。
MOTOMUR

2017/10/22 12:37

ref()の中身に苦戦していましたが、登録したデーターベースの名前を””で囲んで入れるとうまくいきました。これでユーザー情報の中身のみ取得できます。console.logで確認したところ、 4VOa8044WNb9Detv59DjyLGjMyz2: explain:"adatda" title:"adta" user:"TTOT" と、取得内容がなっていました。 この場合、この値たちはどうやって参照したらいいのでしょうか、試行錯誤してみますが、アイディアあればお願いします。
MOTOMUR

2017/10/22 12:37

左はデーターベースの種別、右はユーザーが打ち込んだ中身です。
MOTOMUR

2017/10/22 12:38

一番上はユーザーIDです
karamarimo

2017/10/22 12:42

おそらくオブジェクトだと思うので普通に const record = snapshot.val(); const explain = record.explain; のように取り出せると思います。
MOTOMUR

2017/10/22 13:37

そうやってみたのですが、console.log('%s',record.title)で表示させるとundifinedとなってしまいます。どうやって取得したらいいんでしょうね?引き続きこちらは試行錯誤いたします。
MOTOMUR

2017/10/22 14:02

今気づいたのですが、"users"にすると、データーベースのユーザー全ての情報を参照してしまう状態のようです。改善します。
karamarimo

2017/10/22 14:22

もしかすると snapshot.val() はこんな感じですか? { 4VOa8044WNb9Detv59DjyLGjMyz2: { explain:"adatda", title:"adta", user:"TTOT"} } そしてこれが"users"の値ということでしょうか? ならば .ref("users/" + <ユーザーID>) みたいにすれば現在のユーザーのデータだけ取り出せるんじゃないでしょうか。 公式ガイド https://firebase.google.com/docs/database/web/read-and-write
MOTOMUR

2017/10/22 14:35

現在あまりに苦戦するため、データーベースの中身の受け取りについてはfirebaseのサポートに連絡して、返事待ちをしております。また、個別のデータの受け取りの部分のコードは現在、そのように改善し、 const myUserId = this.state.userId firebase.database().ref('/users/'+myUserId).once('value').then((snapshot) のように個別のデータを受け取れるようにいたしました。中身がundifinedである現状はすぐに改善策が思いつかない場合は放置し、userの投稿が可能になるように投稿のビューを作成しようかなと思います。
MOTOMUR

2017/10/22 14:39

現在のコンソールの表示は、 4VOa8044WNb9Detv59DjyLGjMyz2: explain:"adatda" title:"adta" user:"TTOT" こんな感じです。
MOTOMUR

2017/10/22 14:41

一応、githubにコンソールログを表示できる状態でコミットしたので、僕の口頭でわかりづらければお手数ですが、ご確認を。
karamarimo

2017/10/22 15:34

今気づきましたが、componentWillMount の中で this.state.userId を取得しようとしていますが、onAuthStateChangedのイベントハンドラーが呼び出されるのはログイン状態が変わったときだけです。なのでそのやり方では uid は取得できません。 componentWillMount 内で firebase.auth().currentUser.uid を取得すればいいと思います。
MOTOMUR

2017/10/23 04:04

コード更新しました。willmountで受け取って、didmountで使ってるんですけど、これではダメってことですか?
karamarimo

2017/10/23 04:32

そのコードでは userId が set されるのはいつでしょうか? componentWillMount より後にログイン状態が変化したときです。普通変わらないですよね。 なので componentDidMount の時点で userId がセットされている保証は全くありません。
MOTOMUR

2017/10/23 07:34

間違っていました。ご指摘ありがとうございます。入れ子を勘違いしていました・・・ 修正コードをあげています。
MOTOMUR

2017/10/23 08:33

現在、username以外は表示できております。というより、title={this.state.username}はできますが、title='mypage for {this.state.username}'はできません。mypage for {this.state.username}と表記されます。それ以外のバグは修正できました。
MOTOMUR

2017/10/23 08:36

Mypage内にmakepostの機能を追加していきます。
karamarimo

2017/10/23 08:39 編集

title={"mypage for " + this.state.username} とすればいいです。{ } 内には任意の js expression が書けます。
MOTOMUR

2017/10/23 10:06

git 質問共に更新しました。投稿のビューで不明点があって、0投稿の時はPOSTEDにてyour posting does not exsist.と表示。投稿があるときは最近の投稿5つを降順(最新の投稿順)で表示させたいです。この場合分けと、降順による表示の考え方がいまいちわかりません。 どうしたら良いでしょうか。
karamarimo

2017/10/23 10:42

データベースから取得した投稿データはどんな形式でしょうか。 render関数内で三項演算子などを用いれば場合分けは簡単です。 時系列順に並んでいるなら最後の5つを取ればいいですね。 demo https://codesandbox.io/s/yv4wkz2y6z
karamarimo

2017/10/23 10:53

.slice(-5) で最後の5つを取り、.reverse() で逆順にしています。
MOTOMUR

2017/10/23 12:59

firebaseによると、 var newPostKey = firebase.database().ref().child('posts').push().key var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return firebase.database().ref().update(updates); で、同時に二つの場所にアップデートする関数を作ることができるようなのですが、これはReactの構文的にはまずいですよね?書き直したらどのようになるでしょうか。この関数をPOSTボタンを押すときに呼び出すようにすればいいということでしょうかね?
karamarimo

2017/10/23 13:18 編集

データベースの2つの場所に同じデータを入れるのはよくないと思いますが(そういう意味で書いていないならすみません)、どこがReact的にまずいとお思いなのでしょうか? > この関数をPOSTボタンを押すときに呼び出すようにすればいいということでしょうかね ユーザーがPOSTボタンを押した時にデータベースにプッシュしたいならそれは当然のような気がするのですが、他にどのような選択肢があるのでしょうか?すみません質問の意図がよく分からないです。
MOTOMUR

2017/10/23 13:27

ごめんなさい。いつもと違う形式なので戸惑いを含む質問でした。。質問内容まとめたのでお願いします。 function writeNewPost(uid, username, title, body) { // A post entry. var postData = { author: username, uid: uid, body: body, title: title, starCount: 0, }; // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/users/' + uid + '/' + newPostKey] = postData; return firebase.database().ref().update(updates); } これが、上書きなしのアップデート関数のようなので、 firebase.database().ref('users/' + this.state.userId+'/posts/').set の代わりにhandleSubmitの中に入れたいのですが、そうした場合、このままhandleSubmitの中に入れていいのか、 さらにfunctionの引数に入れたい値を代入しておいてこれを呼び出すだけでいいのか、それともpostDataの中をいじればいいのか。わからないです! こうじゃないパターンとしては、これを関数として定義しておき、それをhandleSubmitの中で呼び出すのかなと思ったのですが、その場合、この関数の呼び出しはfirebase.database().ref().update(updates)でしょうか? となると引数は定義したところでstateを使って設定しておくんですかね?
MOTOMUR

2017/10/23 13:35

handleSubmit=(event) =>{ const self = this function writeNewPost(self.state.userId, self.state.username, self.state.post_title, self.state.post_body) { // A post entry. var postData = { author: self.state.username, uid: self.state.userId, body: self.state.post_body, title: self.state.post_title, starCount: 0, }; // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/users/' + self.state.userId + '/user_posts/' + newPostKey] = postData; return firebase.database().ref().update(updates); } }; こうやって書いてみたのですが、「Syntax error: Unexpected token, expected , 」と表示されるので、state入れられないのですかね?
MOTOMUR

2017/10/23 13:45

申し訳ないです。めちゃめちゃいじったらうまくいきました。完成したらコードあげます。
karamarimo

2017/10/23 14:00

writeNewPostの関数定義の引数に、実際の値を書いてるせいです。そこは引数名を書く所です。 writeNewPost は handleSubmit などと並列にメソッドとして定義してはどうでしょうか。 あと、 postData のキー名が公式ガイドそのままですが、それは自由ですよ。 私もfirebaseをよく理解してないのでなんですが、投稿を2箇所に保存する必要が本当にあるのか少し気になります。
MOTOMUR

2017/10/23 14:15 編集

了解です。サイトの構造を踏まえつつ、そこの扱いについては今後考えることにします。アドバイスありがとうございます。 現在、karamarimoさんがくれたデモを元に、ポストが0の時、No entryとするために書いてみたのですが、 <Tab label="POSTED" value="b"> <div> { this.state.posts.length === 0 ? <div><h>No entries</h></div> : <div className='col s12 m4'> <Card> <CardTitle title={this.state.post_title} subtitle={this.state.username} /> <CardText> {this.state.post_body} </CardText> </Card> </div> } </div> </Tab> こうすると、 this.state.posts.length === 0 の部分のlengthがnullだと言われるのですが、初期値の与え方が悪いのでしょうか、それとも、別の条件で分岐しないとまずいんですかね?(ポストを複数しているユーザーと0のユーザーそれぞれでテストしましたが、0のユーザーだけの症状です。) それと、今後の直近の過去ポスト5つを表示する機能について、確認したいのですが、今回のデモの場合、 this.state.items.slice(-5).reverse().map( item => <li>{item.name}</li>、としていますが、これをmaterial ui <card>に載せる場合、どうしたら良いのでしょうか?
karamarimo

2017/10/23 14:46

> this.state.posts.length === 0の部分のlengthがnullだと言われるのですが そもそも this.state.posts をセットしているコードが見当たらないですが...。 > これをmaterial ui <card>に載せる場合、どうしたら良いのでしょうか? <li>{item.name}</li> を次のようにすればいいと思います。 <Card> <CardTitle title={item.post_title} subtitle={item.username} /> <CardText> {item.post_body} </CardText> </Card>
MOTOMUR

2017/10/24 04:49

ありがとうございます。コードの更新しておらず、postsが抜けておりました。修正コードをあげました。 デモのコードで不明点があるのですが、 this.state.items.slice(-5).reverse().map( item => <li>{item.name}</li> これのitemってどこで出てきたitemなのかということと、item => の機能がよくわかっていません。教えていただけないでしょうか。
MOTOMUR

2017/10/24 04:53

現在、新しいコードに修正しています。 自分のコードでいうpost(デモでいうitem)がundefinedとなるためこのような質問をしました!
karamarimo

2017/10/24 05:30 編集

item => <li>{item.name}</li> は arrow function といって、この場合 function (item) { return <li>{item.name}</li> } と同じです。配列のmap関数は分かりますか?ご存じないなら調べてみてください。 おそらく self.setState({ posts: snapshot.val() }); でセットした値が配列じゃないからだと思います。 firebaseでは配列ではなくランダムに生成されたキーでリストを管理するみたいですね。 一度↓こんな感じで snapshot.val() の中身を出力してここにコピペしてもらえますか? console.log(JSON.stringify(snapshot.val()));
MOTOMUR

2017/10/24 05:37

map関数を勉強しておきます。 {"-Kx8TSa9k0axbP2tWuY2":{"author":"MOTO","body":"Guys","starCount":0,"title":"Hey","uid":"tkI5FZ6gizduCcdsIUwY1iObxUX2"},"-Kx8VphkWvgdIR0bplWn":{"author":"MOTO","body":"2","starCount":0,"title":"2","uid":"tkI5FZ6gizduCcdsIUwY1iObxUX2"},"-Kx8VvXibSfWiGPZb23u":{"author":"MOTO","body":"3","starCount":0,"title":"3","uid":"tkI5FZ6gizduCcdsIUwY1iObxUX2"},"-Kx8WMb_ysQWdxOIjC_N":{"author":"MOTO","body":"","starCount":0,"title":"4","uid":"tkI5FZ6gizduCcdsIUwY1iObxUX2"}} console.log(JSON.stringify(snapshot.val()));やってみました。これで大丈夫ですかね?
karamarimo

2017/10/24 07:56

ありがとうございます。 やはり直接 snapshot.val() でオブジェクトにしてしまうと時間順に投稿を取り出すのが面倒ですね。 調べてみると forEach メソッドを使えばそれができるようです。 https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach これを使って投稿の配列を作り state に入れればいいのではないでしょうか。 const posts = []; snapshot.forEach(childSnapshot => posts.push(childSnapshot.val()); this.setState({ posts: posts });
MOTOMUR

2017/10/24 12:27

<div> <span>{ this.state.posts.length }</span> { this.state.posts.length === 0 ? <div><h>There is no post</h></div> : <ul>{ this.state.items.slice(-5).reverse().map( function(post){ <div className='col s12 m4'> <Card> <CardTitle title={post.post_title} subtitle={this.state.username} /> <CardText> {post.post_body} </CardText> </Card> </div> } ) }</ul> } </div> 現在これがうまくいきません。Unhandled Rejection (TypeError): Cannot read property 'slice' of undefined だそうですが、よくわからんエラーです。 const posts = []; snapshot.forEach(childSnapshot => posts.push(childSnapshot.val()); this.setState({ posts: posts }); この件は firebase.database().ref('/users/'+myUserId+'/user_posts/').once('value').then(function(snapshot) { snapshot.forEach(function(childSnapshot){ const posts = []; posts.push(childSnapshot.val()), self.setState({ posts: posts }); }) }); } このように導入して、うまく作動してるのは確認できました。ありがとうございます。
MOTOMUR

2017/10/24 12:28

ちなみに、ポストが0のアカウントの動作は可能になりました。ありがとうございます。 今回のエラーは1つ以上ポストのあるアカウントです。
karamarimo

2017/10/24 12:42

this.state.items.slice(-5) ではなく this.state.posts.slice(-5) ですね。
MOTOMUR

2017/10/24 12:58

ありがとうございます。ケアレスミスです。コンパイルは通ったのですが、カードが表示されません。 また、4ポストあるはずですが、<span>{ this.state.posts.length }</span>が1とだけ表示されるだけです。どうしたら良いでしょう。
karamarimo

2017/10/24 13:07

snapshot.forEach(function(childSnapshot){ const posts = []; posts.push(childSnapshot.val()), self.setState({ posts: posts }); }) ではなく const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()); }) self.setState({ posts: posts }); ですね...。なぜだか分かりますか? また <CardTitle title={post.post_title} subtitle={post.username} /> <CardText> {post.post_body} と書いていますが、post は先程貼っていただいたように {"author":"MOTO","body":"Guys","starCount":0,"title":"Hey","uid":"tkI5FZ6gizduCcdsIUwY1iObxUX2"} という形なので、post_title などのプロパティは存在しませんよ。
MOTOMUR

2017/10/24 13:16

上記だと。ファンクション後に消失してしまう。ですか?自信ありませんが。 stateやらデータベースやらをいじりすぎてこんがらかってしまいますね。ご指摘ありがとうございます。助かりました。 そのようにコード修正を本文に貼らせていただきます。 さて。直してコンパイルして動作を見てみたのですが、posts.lengthは4となっていて正しいのですが、 カードがやはりどうしても表示されません。 どこがおかしいのでしょうか。 動作確認可能なようにgithubも更新しておきますね。
karamarimo

2017/10/24 13:57

> ファンクション後に消失してしまう。ですか? まぁそういうことです。ループのたびに posts が [] に戻ってしまいます。 >カードがやはりどうしても表示されません。 map(function(){ ... }) 内で何も return していないからです。 this.state.posts.slice(-5).reverse().map( function(post){ return ( <div className='col s12 m4'> <Card> <CardTitle title={post.title} subtitle={post.author} /> <CardText> {post.body} </CardText> </Card> </div> )})
MOTOMUR

2017/10/24 14:06

表示できました!!!!ありがとうございます!!!!
MOTOMUR

2017/10/24 14:10

ホームのビューも同じようにいじってみます。 ところで、mypageにて新しい投稿をしたら、同じページにリダイレクト(日本語あってるかな?ページ更新)して、すぐ新しい投稿を表示できるようにしたいのですが、その場合は何をしたら良いでしょう。
karamarimo

2017/10/24 14:19

表示を更新するために同じページに飛ぶというのはぎこちないですね。 今、componentDidMount にて".once"で一度だけデータをリクエストして取得していますよね。 それを".on"にすれば、いつでも値が変わった時に取得できますね。
MOTOMUR

2017/10/24 14:39

var onValueChange = firebase.database().ref('/users/'+myUserId+'/user_posts/').on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()), self.setState({ posts: posts }); }) }); this.setState({off:onValueChange}) ↑---------------------didMount componentWillUnmount() { const self = this this.state.unsub(); firebase.database().ref.off('value', self.state.off) }; としたのですが、 TypeError: __WEBPACK_IMPORTED_MODULE_5_firebase___default.a.database(...).ref.off is not a function となります。https://firebase.google.com/docs/reference/js/firebase.database.Reference#off を参照したのですが、何が間違っているのでしょう。。
MOTOMUR

2017/10/24 15:00

むむ。色々トラブルケーシングをしてたところ、makeMyPageに行こうとする時のみのバグっぽいので大丈夫かな?Home、Settingには飛べるので、大丈夫そうです。今後、直すべきバグなのかな?とりあえず機能には問題なさそうです!
MOTOMUR

2017/10/24 15:01

いや、ダメな時はhomeもダメですね。とりあえず自分はhomeのビューを作ります。
karamarimo

2017/10/24 15:06

firebase.database().ref.off('value', self.state.off) ref のあとに () が抜けてます。ただ".on"した所と同じrefに対して".off"しないといけないと思うので、 firebase.database().ref('/users/'+this.state.userId+'/user_posts/').off('value', self.state.off) ですね。
MOTOMUR

2017/10/24 15:43

ありがとうございます。修正できました! 現在、ホームのビューを作成しています。今後ホームに実装する機能として、 ①スターをつけたユーザーのカードを表示し、そこには最新のユーザーの記事を載せていきます。 ②ユーザーの検索機能。 これがないと①の機能を使いづらいのでなんとかしたいです。 ③記事ごとのコメント機能  これはぜひほしい機能ですが、1、2に比べると重要性が薄く、まだまだ先でいいです。 という状況です。下のコメントにそれぞれの進行状況と、困りそうなことを列挙します。
MOTOMUR

2017/10/24 15:52

①とりあえず大枠はできました。データ管理で苦心しています。とりあえず、スターした時の機能として、ユーザーのデータの子に新たに/stared_users/を作り、その中に、スターをつけたユーザーIDを保持させればいいと思います。 そして、そのIDを参照して、最新の投稿のみを取ってくればいいと思うのですが、複雑になってきており混乱しています。 componentDidMount(){ const myUserId = this.state.userId const self = this var onValueChange = firebase.database().ref('/users/'+myUserId+'/stared_users/').on('value',function(snapshot) { const stared_users = []; snapshot.forEach(function(childSnapshot){ stared_users.push(childSnapshot.val()), self.setState({ stared_users: stared_users }); }) }); このようにし、stateの stared_usersが0の場合はThere is no stared userとするところまではできています。 スター機能をつけ、スターしたユーザーを参照できるようになったら、stateのpostsに(これはユーザーの最新投稿を保存)に var onValueChange = firebase.database().ref('/users/'+stared_UserId+'/user_posts/').on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()), self.setState({ posts: posts }); }) }); こうしていけば良いのでしょうが、stared_UserIdをどのように制御したら良いのかわからず、ここで手詰まりそうだなと思っています。教えていただけると助かります。それと、これでは全てのポストを受け取ってしまうような気がするのですが、最新の投稿を取るにはどうしたらいいか。も知りたいです。
MOTOMUR

2017/10/24 15:54

②このwebサイトに検索エンジンを導入するにあたり、どのようなフレームワークやライブラリ、もしくは外部のアプリ等、がオススメでしょうか?初期搭載としては、あまりこだわらなくていいので、簡単に導入できるものがいいです。オススメを教えてください。
MOTOMUR

2017/10/24 15:56

③これはpostsの子としてcommentsを入れてその中に、コメントたちを記事ごとに管理して表示していけばいいかもしれませんね。文章書いてたらなんとなく考えがまとまってきました。しかし、レイアウト的に考えて、カードじゃコメントと投稿の区別がわからなくなってしまうので、その辺をうまくレイアウトする方法が難しいなと思います。簡単な方法あればなと思います。
MOTOMUR

2017/10/24 15:57

いっぺんにたくさんの質問をしてしまい申し訳ありません。少しずつ、可能な時に回答いただければ嬉しく思います。
karamarimo

2017/10/25 02:49

> これでは全てのポストを受け取ってしまうような気がするのですが そうですね。最新のだけ取ってくるほうがいいですね。 .slice(-5) で最新の5つを表示するようにはしていましたが、それ以外表示しないなら無駄ですね。 > 最新の投稿を取るにはどうしたらいいか https://firebase.google.com/docs/database/web/lists-of-data#filtering_data ここを見ると limitToLast() というメソッドがあるのでこれを使えばいいのではないでしょうか。 > このwebサイトに検索エンジンを導入するにあたり、どのようなフレームワークやライブラリ、もしくは外部のアプリ等、がオススメでしょうか? 結局データはFirebaseにあるので、Firebaseの検索機能を使うか、外部にインデックスを作るしか無いと思います。 しかしFirebaseのRealtime DBには部分文字列検索の機能がありません。 完全一致検索や先頭文字列検索(例えば"Firebase"が"Fire"でヒット)ならできます。 部分文字列検索は必要でしょうか?
MOTOMUR

2017/10/25 03:51

ざっと調べたところ、firebaseの検索機能を見つけられません。お手数ですが、urlをいただけると幸いです。 最新の投稿は試行錯誤してみます。
MOTOMUR

2017/10/25 03:54

それと、軽微なバグなのですが、HomeとMyPageにいるときに、ブラウザのリロードをすると、 TypeError: Cannot read property 'uid' of null となるのですが、unsubの導入箇所が悪いのでしょうか。componentWillMount内に現在あります。
karamarimo

2017/10/25 04:16

> firebaseの検索機能を見つけられません 明示的に検索機能とは書いてないですが、先程の limitToLast() と同じところに startAt() と endAt() があり、これを使うと先頭文字列による検索ができます。 参考 https://stackoverflow.com/questions/38618953/how-to-do-a-simple-search-in-string-in-firebase-database > TypeError: Cannot read property 'uid' of null エラーとともにソースコードの該当箇所か行番号が表示されると思うのですが、それも教えていただけますか?
karamarimo

2017/10/25 05:24

> カードじゃコメントと投稿の区別がわからなくなってしまうので、その辺をうまくレイアウトする方法が難しいなと思います。 1つのカードに投稿とコメントをまとめてもいいんじゃないでしょうか。こんな感じで。 https://codesandbox.io/s/7w8mv8oz0q
MOTOMUR

2017/10/25 07:44

> 31 | userId : firebase.auth().currentUser.uid ここがエラーになります。 検索機能の件了解です。導入検討してみます。 カードのレイアウトの件も、この感じで導入してみます。
karamarimo

2017/10/25 07:50

リロードすると認証が外れるからでしょうか...。 他のページではリロード時にログイン状態は維持されますか?
MOTOMUR

2017/10/25 08:34

ええと、home MyPageはリロードすると表示できなくなり、さっきの警告です。 そうなった状態で、loginとsignupのURLを直にうつと表示できます。 そもそも、login signupは認証されていたら入れないページです。 なんでエラーなのかはよくわからないです。
karamarimo

2017/10/25 08:55

Home が表示できない理由はわからないですが、MyPageについては次のような理由ではないでしょうか。 リロード直後はApp.js で initializeApp が実行されますが、認証が完了する前に MyPage の componentWillMount() が実行され、currentUesr が null になってしまうからだと思われます。この場合は、認証が完了すると onAuthStateChanged のコールバックが呼び出されるので、その中で uid と投稿を取得しましょう。 なので、まず componentDidMount() の中身を取り出して1つの関数にしましょう(仮に fetchPosts とする)。 componentWillMount() 内で currentUesr がもし null でなければ this.state.userId を更新し「その後に」 fetchPosts を実行します。また onAuthStateChanged のコールバックで、user が null でない ならば this.state.userId を更新し、「その後に」fetchPosts を実行します。 注意すべき点は setState() は非同期です。 https://reactjs.org/docs/react-component.html#setstate 第2引数に、state が更新された時に呼び出されるコールバックを指定できるので、これに fetchPosts を指定するといいと思います。
MOTOMUR

2017/10/25 09:50

アドバイスを参考に、 fetchPosts(){ const myUserId = this.state.userId const self = this firebase.database().ref('/users/'+myUserId).once('value').then(function(snapshot) { self.setState({ username: snapshot.val().user, title: snapshot.val().title, explain: snapshot.val().explain }); }); var onValueChange = firebase.database().ref('/users/'+myUserId+'/user_posts/').on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) self.setState({ posts: posts }); }) }); this.setState({off:onValueChange}) } componentWillMount(){ const self=this var unsub = firebase.auth().onAuthStateChanged(function(user){ if(user){ self.setState({ userId : firebase.auth().currentUser.uid }); fetchPosts() } else { self.props.history.push('/login') } }) this.setState({unsub}); }; componentDidMount(){ } としたのですがwillmount内のfetchPostsが'fetchPosts' is not defined no-undef となります。どこがミスってるのでしょうか。
karamarimo

2017/10/25 10:39

メソッドなので this.fetchPosts としなければいけません。 また前コメントの通り this.setState のコールバックに指定しないと上手くいかないと思います。
MOTOMUR

2017/10/25 21:57

mypageのuidの件、直せました。ありがとうございます。 現在検索のコードを作成中なのですが、検索の入力、検索をするボタンを <Card> <CardText> <TextField id="search" floatingLabelText="type username for search" value={this.state.search} onChange={this.handleChange_search} /> <RaisedButton label="Search" secondary={true} onClick={this.handleSearch} /> </CardText> </Card> ハンドルサーチを handleSearch = (event) =>{ const self = this firebase.database().ref().startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value") this.setState({searchReference:'1'}); }; このようにし、searchReferenceが0の時は何も表示しない状態ですが、検索し1になると、検索結果のカードを出力するように作成したつもりなのですが、 <div> { this.state.searchReference === 0 ? <div><h>There is no such user</h></div> :<ul>{ this.state.posts.slice().reverse().map( function(stared_user){ return( <div className='col s12 m4'> <Card> <CardTitle title={stared_user.title} subtitle={stared_user.author+ " star:"+stared_user.starCount} /> <CardText> a {stared_user.body} </CardText> </Card> </div> ) } ) }</ul> } </div> この部分が、0から1になったときにカードが出てきません。カードの内容はデバック用なので適当で、カードテキストのaが表示できればいいかなといった状態なので、詳細は気にしないで欲しいのですが、 なぜ検索ボタンが押されたときに、条件式の処理が行われないのでしょうか? また、この治しかたを教えてください。
MOTOMUR

2017/10/25 21:59

コードの全文を、わかりづらいと思うので、質問文に更新します。 あと、検索のリファレンスを firebase.database().ref().startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value") としたのですが、コードはまだ未完でしょうか?いまいちコードの処理が分かっていません。
karamarimo

2017/10/26 01:50

> コードはまだ未完でしょうか? "value"イベントのリスナーを渡していないので、意味がないですね...。また ref() (ルート)で検索しているのでうまくいかないでしょう。 あと this.posts がいろんなところで使われているのと、fetchPosts メソッドが見当たらないですが大丈夫でしょうか?
MOTOMUR

2017/10/26 03:19

ごめんなさい。現在、searchReferenceによる、renderの制御を優先的にやってまして、this posts fetchPostsは考え中で、コピーのまま、置いてあったりします。わかりづらい状況での質問で申し訳ないです。 それで、searchReferenceによる、検索ボタンを押したときにrenderに検索結果をカードで表示する機能ですが、ボタンを押しても表示されません。ライフサイクルの問題でしょうか? ユーザーの入力によって、searchReferenceを1や0にすることで、renderの条件分岐をすることでは、表示を変えることはできないのでしょうか?コーディングミスでしょうか? 該当のコードは、自分のこのコメントの3つ前に抽出して書いております。
karamarimo

2017/10/26 04:33

まず this.state.searchReference は '0' か '1' にセットされるので "=== 0" と比較するのではなく '0' ですね。
karamarimo

2017/10/26 04:46

検索結果に仮に posts を表示しようとされているのだと思いますが、posts を取得するコードがないので何も表示されていないのだと思います。適当に初期値を設定されてはどうでしょうか?
MOTOMUR

2017/10/26 08:28

ありがとうございます。 初期値も変えて、this.state.searchReferenceによる制御が可能になりました。 fetchPosts メソッドの作成に入りたいのですが、 fetchPosts(){ const self = this const myUserId = self.state.userId const staredUser=self.state.stared_users var onValueChange1 = firebase.database().ref('/users/'+myUserId+'/stared_users').on('value',function(snapshot) { const stared_users = []; snapshot.forEach(function(childSnapshot){ stared_users.push(childSnapshot.val()) self.setState({ stared_users: stared_users }); }) }); this.setState({off_stared_user:onValueChange1}) var onValueChange2 = firebase.database().ref('/users/'+staredUser+'/user_posts/').on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) self.setState({ posts: posts }); }) }); this.setState({off_stared_user_posts:onValueChange2}) } このような感じで、onValueChange1のときに、スターをつけたユーザーのIDを取ってきて、stateの stared_usersに配列で保存するところまではいい感じだと思うのですが、 onValueChange2のstateの stared_usersの数だけ、繰り返しの処理を行い、最新の投稿を取ってくる。というのが少し困っています。 this.state.stared_users.lengthで数を取ってこれるのか。 繰り返しの処理のさせかたはどうやるのか。 わかりません。教えてください。
MOTOMUR

2017/10/26 08:28

現在のコードを更新しました
karamarimo

2017/10/26 08:59

とりあえず setState は forEach の外に出したほうがいいですね。配列に追加するたびに state を更新する必要性がありません。
MOTOMUR

2017/10/26 09:29

はい。出しました。質問文のコード更新します。
MOTOMUR

2017/10/26 09:41

onValueChange2にとりあえずlimitToFirst(1)を追加しました。
MOTOMUR

2017/10/26 09:45

limitToLast(1)と迷いましたが、とりあえず動かしてみてからですね。
karamarimo

2017/10/26 10:44

fetchPosts はcomponentWillMountで実行されますが const staredUser=self.state.stared_users だと staredUser == [] になってしまいますね。
karamarimo

2017/10/26 10:53

繰り返しは普通に for 文でやればいいと思いますが、ご存じないのでしょうか? for (var i = 0; i < ###.length; i++) { // ###[i] を使う } "/stared_users" が変化したら、その各ユーザーについて .on ではなく .once で一度だけ投稿を取得するのが楽だと思いますよ。もちろんリアルタイムに更新されなくなりますが。 もし .on でリアルタイムに取得するならイベントリスナーの unsubscribe 関数をそのユーザー数だけ state に保存する必要がありますね。また、"/stared_users" が変化したらその度に全部 unsubscribe する必要があります。
MOTOMUR

2017/10/26 11:22

なるほど!C言語とかのように繰り返しできるんですね。stateの配列も普通の配列と同じ扱いできるんですね。こんな感じでコーディングしてみました。 fetchStaredUser(){ const self = this const myUserId = self.state.userId var onValueChange1 = firebase.database().ref('/users/'+myUserId+'/stared_users').on('value',function(snapshot) { const stared_users = []; snapshot.forEach(function(childSnapshot){ stared_users.push(childSnapshot.val()) }) self.setState({ stared_users: stared_users }); }); this.setState({off_stared_user:onValueChange1}) } fetchStaredUserPosts(){ const self = this for(var i = 0 ; i < self.state.stared_users.length ; i++){ const staredUser = this.state.stared_users[i] var onValueChange2 = firebase.database().ref('/users/' + staredUser + '/user_posts/').limitToFirst(1).on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) }) self.setState({ posts: posts }); }); this.setState({off_stared_user_posts:onValueChange2}) } } componentWillMount(){ const self=this var unsub = firebase.auth().onAuthStateChanged(function(user){ if(user){ self.setState({ userId : firebase.auth().currentUser.uid },self.fetchStaredUser); self.fetchStaredUserPosts } else { self.props.history.push('/login') } }) this.setState({unsub}); } スターしたユーザーのIDを取ってくるのと、表示するのを分け、componentWillMountで順番に呼び出すようにしました。 スター機能がまだないので、デバックはできていないのですが、この部分はいい感じな気がします。 どこか行けないところがあればお願いします。質問文のコード、更新しますね。
MOTOMUR

2017/10/26 12:01

現在、名前検索機能を追加しているんですが、データベース上の管理方法が users/(userId)/user という感じで、名前が各ユーザーIDの子となっていて、どうやってrefしていいかわかりません。 firebase.database().ref('/users/'+allUserId+'/user/')startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value").then(function(snapshot) ここのallUserIdをどうやって行えばいいんですかね? また、ユーザーの名前を参照したあと、そのユーザーのIDを返さなければ行けないんですが、これもまた難しいなと思いました。 どうしたらいいのでしょうか。
MOTOMUR

2017/10/26 14:01

今、stared usersのビューを作っているのですが、ライフサイクルメソッドの順番を考えて、 下のコードのようにユーザーIDを取得し、その後にユーザーのポストを表示させようとしてるのですが、 console.logを使って確認すると、ユーザーのポストのコンソールのHelloが、ユーザーのIDのsnapshotの結果よりもかなり先に出てしまうのですが、どうやって改善できるでしょうか。 /*------------refer stared user-------------------------------------------------------------------*/ fetchStaredUser(){ const self = this const myUserId = self.state.userId var onValueChange1 = firebase.database().ref('/users/'+myUserId+'/stared_users').on('value',function(snapshot) { const stared_users = []; snapshot.forEach(function(childSnapshot){ stared_users.push(childSnapshot.val()) console.log(childSnapshot.val()) }) self.setState({ stared_users: stared_users }); }); this.setState({off_stared_user:onValueChange1}) } fetchStaredUserPosts(){ const self = this console.log("Hello") for(var i = 0 ; i < self.state.stared_users.length ; i++){ const staredUser = this.state.stared_users[i] console.log(staredUser) var onValueChange2 = firebase.database().ref('/users/' + staredUser + '/user_posts/').limitToFirst(1).on('value',function(snapshot) { const posts = []; console.log(snapshot.val()) snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) }) self.setState({ posts: posts }); }); this.setState({off_stared_user_posts:onValueChange2}) } } /*------------life cicle method--------------------------------------------------------------------*/ componentWillMount(){ const self=this var unsub = firebase.auth().onAuthStateChanged(function(user){ if(user){ self.setState({ userId : firebase.auth().currentUser.uid },self.fetchStaredUser); } else { self.props.history.push('/login') } }) this.setState({unsub}); } componentDidMount(){ const self=this this.setState({},self.fetchStaredUserPosts); }
karamarimo

2017/10/26 15:31

> users/(userId)/user という感じで、名前が各ユーザーIDの子となっていて、どうやってrefしていいかわかりません。 user というパスはなく userId の直下に author とか body とかあったと思うんですが...。 あるキーの値についてソートするには .orderByChild(path) を使います。 https://firebase.google.com/docs/reference/node/firebase.database.Reference#orderByChild ユーザー名は author ですか?(英語として間違っていると思いますが) こんな感じですね。 firebase.database().ref('/users/').orderByChild('author').startAt(self.state.search).endAt(self.state.search+"\uf8ff")
karamarimo

2017/10/26 16:28

> ユーザーのポストのコンソールのHelloが、ユーザーのIDのsnapshotの結果よりもかなり先に出てしまうのですが、どうやって改善できるでしょうか。 fetchStaredUser も fetchStaredUserPosts も非同期に取得するので、ちゃんとしないとどちらが先に取得されるかは保証されません。 そもそも componentWillMount と componentDidMount に分ける意味があまりないと思います。componentWillMount で実行した非同期処理は componentDidMount までに終了する保証はどこにもありません。 また for loop の中で self.setState({ posts: posts }) this.setState({off_stared_user_posts:onValueChange2}) を実行しているので最後のユーザーのだけ残りますよ。 ちなみにどうでもいいですが stared ではなく starred ですよ。
MOTOMUR

2017/10/27 02:24

Promiseで順序を取ろうとコードを組んだのですが、 const stateStarredUsers = this.state.starred_users の部分がUnhandled Rejection (TypeError): Cannot read property 'state' of undefined となってしまいます。 Promiseの記事を読んで書いて見ましたが、そもそも使い方を間違っているかもしれません。 どうしたらいいでしょうか。 /*------------refer starred user-------------------------------------------------------------------*/ fetchStarredUser(){ const self1 = this const myUserId = self1.state.userId var onValueChange1 = firebase.database().ref('/users/'+myUserId+'/starred_users').on('value',function(snapshot) { const starred_users = []; snapshot.forEach(function(childSnapshot){ starred_users.push(childSnapshot.val()) console.log(childSnapshot.val()) }) self1.setState({ starred_users: starred_users }); }); this.setState({off_starred_user:onValueChange1}) } fetchStarredUserPosts(){ const self2 = this const posts = []; const stateStarredUsers = this.state.starred_users console.log("Hello") for(var i = 0 ; i < stateStarredUsers.length ; i++){ const starredUser = self2.state.starred_users[i] console.log(starredUser) var onValueChange2 = firebase.database().ref('/users/' + starredUser + '/user_posts/').limitToFirst(1).on('value',function(snapshot) { console.log(snapshot.val()) snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) }) }); } self2.setState({ posts: posts }); self2.setState({off_starred_user_posts:onValueChange2}) } fetchStarAll(){ const self = this var p1 = new Promise( function (resolve, reject) { resolve( self.fetchStarredUser )} ) p1.then( self.fetchStarredUserPosts ) }
MOTOMUR

2017/10/27 02:25

検索は未だに結果がnull以外でない状態です。
MOTOMUR

2017/10/27 02:26

結果というより、 firebase.database().ref('/users/').orderByChild('author').startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value").then(function(snapshot) { console.log(snapshot.val()) このスナップショットがnullです。
karamarimo

2017/10/27 03:19

Promise の使い方が間違ってますね...。 Promise を使わないとするとこうするでしょうか。 https://pastebin.com/81bBRbV5 先程述べた理由で投稿を取得するところは .on ではなく .once を使っています。 また、各ユーザーについて非同期に投稿を取得しているため、取得するたびに posts を setState せざるを得なくなっています。 この点 Promise は Promise.all で合流できるので毎回 setState しなくて済みますね。 しかしPromise でやるなら .on とは相性が悪いです。複数回発生するイベントに Promise は使えません。 いっそ全部 .once にしてはどうですか?リアルタイム性は失われますが。
karamarimo

2017/10/27 03:23

> 検索は未だに結果がnull以外でない状態です。 検索クエリは大文字と小文字が区別されますが、それに気をつけてもダメですか?
MOTOMUR

2017/10/27 03:31

>検索クエリは大文字と小文字が区別されますが、それに気をつけてもダメですか? 関係なくダメです。どうしてでしょうか。 >いっそ全部 .once にしてはどうですか?リアルタイム性は失われますが。 一度そうしてみます。
karamarimo

2017/10/27 03:47 編集

やっと気づきました。 firebase.database().ref('/users/').orderByChild('author').startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value").then(function(snapshot) { ... }); で then は使えません。 EDIT: すみません間違えました。使えます。
karamarimo

2017/10/27 03:58

勘違いしていましたが、"author"じゃなくて"user"でしたね。
MOTOMUR

2017/10/27 12:02

ありがとうございます。全てdIdmountに入れました! 検索機能は、orderBy(user)で参照し、ユーザー直下にユーザーIDの項目を追加し、実現しました。表示のビューを作成中です。 コメント機能を作成してたのですが、ポストごとにコメントの子を追加しなければならず、そこの管理をどうしようか困っています。どうしたら良いでしょうか。
MOTOMUR

2017/10/27 14:15

ありがとうございます。並行して、スター機能を作成していたのですが、 /*------------add star user------------------------------------------------------------------------*/ starBtnClick = (value) =>{ const self = this this.setState({willStarUser:value},this.allAddFunction) } addStarUserToDataBase() { const myUserId = this.state.userId var starUserData = this.state.willStarUser; var newStarUserKey = firebase.database().ref('/users/' + myUserId + '/starred_users/').push().key; var updates = {}; updates['/users/' + myUserId + '/starred_users/' + newStarUserKey] = starUserData; return firebase.database().ref().update(updates); } resetWillStarUser(){ this.setState({ willStarUser:'' }); } allAddFunction(){ const self = this this.addStarUserToDataBase(function() { self.resetWillStarUser() }) } このようにし。 <div> { this.state.searchReference === '0' ? <div><h>here is search reference view</h></div> :<ul>{ this.state.search_users.slice().map( function(searchRef){ const self = this return( <div className='col s12 m4'> <Card> <CardTitle title={searchRef.user} subtitle={searchRef.title} /> <CardText> {searchRef.explain} </CardText> <CardActions> <FlatButton primary label="STAR" onClick={this.starBtnClick} value={searchRef.uid} /> </CardActions> </Card> </div> ) } ) }</ul> } </div> こうしたのですが、 <FlatButton primary label="STAR" onClick={this.starBtnClick} value={searchRef.uid} /> が Unhandled Rejection (TypeError): Cannot read property 'starBtnClick' of undefined と言われます。 どうしたらいいでしょうか? そもそもユーザーIDをカードに乗せたボタンに格納できているのか不明ですが。 それが不能の場合、どのような方法があるでしょうか。
karamarimo

2017/10/27 14:43

一応、一部分を取り出すとかでなければ .slice() は不要です。 this.starBtnClick でエラーが出るのは、this が undefined になるからで、それは関数内だからです。 楽な方法は bind することです。 .map(function(searchRef){ ... }.bind(this)) また、更新する箇所が1つなら var updates = {}; などを用いてやる必要はありません。 https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data
MOTOMUR

2017/10/27 15:09

ありがとうございます。コンパイルは通ったのですが、 addStarUserToDataBase() { const myUserId = this.state.userId var starUserData = this.state.willStarUser; var newStarUserRef = firebase.database().ref('/users/' + myUserId + '/starred_users/').push() var path = newStarUserRef.toString() newStarUserRef.set({ path:starUserData }) この newStarUserRef.set({ の部分が、 Error: Reference.set failed: First argument contains a function in property 'users.tkI5FZ6gizduCcdsIUwY1iObxUX2.starred_users.-KxTMmyDiuvvR2BAZcdk.path.currentTarget' with contents = function () { return arg; } こんな感じのエラーが出るのですが、何がダメなのかさっぱりです。
MOTOMUR

2017/10/27 15:14

少し考察しましたが、もしかしたら、ボタンからvalueを取ってくるのは無理なのかもしれませんね。
karamarimo

2017/10/27 15:18

セットしようとしてるデータに関数が含まれてますよ、と言っています。 starBtnClick で willStarUser に event object をセットしているからではないでしょうか(ボタンクリックのイベントハンドラーには当然 event object が渡されます)。
MOTOMUR

2017/10/27 15:21

値をボタンから持ってくるにはどうしたらいいでしょうか。
karamarimo

2017/10/27 15:21

value属性などを使わずとも <FlatButton primary label="STAR" onClick={() => this.starBtnClick(searchRef.uid)} /> とすればいいのではないでしょうか。
MOTOMUR

2017/10/27 15:43

ありがとうございます!とりあえずスター済みかどうかに関わらず、無限にスターする機能は追加できました。 同一のユーザーのスターをできるのは良くないので、スター済みの場合はボタンをSTARRDと表示させたいです。スター済みのユーザーかどうかを判定させるにはどうしたらいいでしょうか。
MOTOMUR

2017/10/27 16:30

現在、スターされた数をデータベースに送る方法で困っています。 /*------------add star user------------------------------------------------------------------------*/ starBtnClick(value){ this.setState({willStarUser:value},this.allAddFunction) } addStarUserToDataBase() { const myUserId = this.state.userId var starUserData = this.state.willStarUser; firebase.database().ref('/users/' + myUserId + '/starred_users/').push().set({ starUserData }) } resetWillStarUser(){ this.setState({ willStarUser:'' }); } allAddFunction(){ const self = this this.addStarUserToDataBase(function() { self.nowStarCount(function(){ self.addStar }) },self.resetWillStarUser) } nowStarCount(){ const self = this const willAddStar = this.state.willStarUser console.log('0') firebase.database().ref('users/' + willAddStar).once("value").then(function(snapshot){ self.setState({ starCount:snapshot.val().userStar }) }) } addStar(){ const self = this var star = this.state.starCount var added = star + 1 console.log('1') this.nowStarCount(function(){ console.log('2') self.setState({starCount:added},self.sendStarData) }) console.log(self.state.starCount) } sendStarData(){ const self = this const willAddStar = this.state.willStarUser console.log(self.state.starCount) firebase.database().ref('/users/' + willAddStar).set({ userStar:self.state.starCount }) } 同期しないと行けないと思い、このようにしたのですが、書いたconsole.logどこにも飛びません。 何がおかしいのでしょうか。
karamarimo

2017/10/28 04:45

まず、ボタンクリック時に this.willStarUser にユーザーIDをセットし他の関数でその値を使う、としていますが、少し回りくどいので直接他の関数に引数として渡したほうがいいです。 また addStarUserToDataBase などに関数を渡していますが、定義を見ると引数が0個なので渡す意味がないですね。
karamarimo

2017/10/28 05:02

また、そのコードでは現在のスター数を取得し、+1 して送信する、としていますが、これはデータベースにおける典型的なまずいやり方です。 もしユーザーAがスター数を取得(nとする)してから増やして送るまでの間に、他のユーザーBもスターを増やそうとしてスター数を取得すると n が得られます。そしてA はスター数 n+1 を送ります。Bも遅れて n+1 を送ります。結局、2人がスターしたのにスター数は n+1 になります。 このような事態を防ぐためにトランザクションというのを使う必要があります。 https://firebase.google.com/docs/database/web/read-and-write#save_data_as_transactions たまたまこれもスター数を更新する例ですが、もちろんコピペせずに適宜自分のコードに合わせて修正してください。
MOTOMUR

2017/10/28 06:55

starBtnClick(value){ const self = this this.addStar(value) } addStar(uid) { const myUserId = this.state.userId const starUserData = uid firebase.database().ref('/users/' + myUserId + '/starred_users/').push().set({ starUserData }).then(function(){ return firebase.database().ref('users/' + uid + '/userStar').transaction(function(count) { if(count){ if(count.userStar&&count.userStar[myUserId]){ count.userStar--; count.userStar[myUserId] = null; }else{ count.userStar++ if (!count.userStar) { count.userStar = {}; } count.userStar[myUserId] = true; } } return count }) }) } コードはこのようになりました。 count.userStar[myUserId] この部分をうまく反映させたいのですが、そもそも、starの数がフォローしても増えません。どこがおかしいでしょうか。
karamarimo

2017/10/28 08:02 編集

データベースをどういう形式にしようとしているのかよく分からないです。 count.userStar++ は数字 count.userStar = {}; はオブジェクト と矛盾していませんか?
karamarimo

2017/10/28 08:28

その例だと、スターしたユーザーのIDを保存する"stars"と、スター数を保存する"starCount"に分かれていますよね。もちろんそれを1つにまとめることはできません。1つのキーに対しては1つの値しかもてません。 > post.stars[uid]はデータベース上ではどう定義してるんでしょうか。 obj["key"] が obj.key と同義なのはご存じですか? つまり uid をキーにして、値を適当に true にセットしています。 こんな感じです。 https://pastebin.com/41N7MMFk 既にご存じでしたらいいですが、データベースの中身はコンソールからいつでも見れますので確認に使ってください。 https://console.firebase.google.com/
MOTOMUR

2017/10/28 10:09

ありがとうございます。スターされた側の制御は完璧に機能するようになりました。 次に、二度スターを押された場合、スターユーザーのリストから消す。というのを .then(function(){ if(add){ return firebase.database().ref('/users/' + myUserId + '/starred_users/').push().set({ starUserData }) }else{ return firebase.database().ref('/users/' + myUserId + '/starred_users/').orderByChild('starUserData').startAt(starUserData).remove() } }) } removeで消せるようなので、このように実装したのですが、(全文は質問文のコードにあります) removeはrefした場所を消すらしく、検索機能で使った .orderByChild('starUserData').startAt(starUserData). のようなごまかしはきかないようです。消せません。 ユーザーIDのパスが '/users/' + myUserId + '/starred_users/' + 重複しないキー の中に starUserData : スターしてるユーザーID のように管理されています。どうしたらいいでしょうか。 一応、データ構造を一部省略し、JSONで書くと、 '/users/' + myUserId + '/starred_users/' :{ 重複しないキー:{ starUsarData : uid } 重複しないキー:{ starUsarData : uid } } みたいな感じです。
karamarimo

2017/10/28 11:51

orderByChild と equalTo を組み合わせれば、特定のキーに特定の値を持つエントリーだけを取得できます。 filtering メソッドを使用すると remove はできないようですね。 .once("value", ) で snapshot を取得すると、 重複しないキー:{ starUsarData : uid } が1つだけ含まれているので、forEach でそれの snapshot を取得し、対応する ref を削除すればいいと思います(たぶん)。 https://pastebin.com/WhpVbJH3
MOTOMUR

2017/10/28 12:13

ありがとうございます!!!!!!!できました!!!!! あとはスターされてたらSTARRED スターしてなかったらSTARとするだけですね!頑張ります!
MOTOMUR

2017/10/28 12:51

と思って、色々書いて見てたのですが、フォロー済みという情報をどうやって取って来ましょう。 先ほどのstarUserDataと検索結果を照合して、判定させなければいけないのですが、 hundleSearchのchildSnapshotがちょうどいいと思い、 そこで判定しようと思います。 childSnapshot.val().userStar.stars にて、uid : true という配列データを取ってこれるのですが、 この、uidと自分のIDを判定する時、 繰り返しの処理はrender内での出来事です。なので、判定し、ボタンを表示するかどうかという情報をchildSnapshotで繰り返しを行い判定したものを格納する必要があります。 render内でのスター表示の判定は、searchRef.starBtnShownが0とか1とかで判定すればいいと思うのですが、(starBtnShownはそれぞれのユーザーがスター済みかどうかの判定を格納する配列) childSnapshot.val().userStar.stars にて、uid : true という配列データと自分のIDを判定する式が怪しいので聞きたいのと、 いつもだと、:の右側のデータを取ってくるので、どうやって左側を受け取るのか、(あるいは、ユーザー IDでデータを取って来て、null、もしくはundefinedだったらスター済みじゃなく、trueだったらスター済みのように判定するのか。) どちらがやりやすいでしょうか。
karamarimo

2017/10/28 13:10

> render内でのスター表示の判定は、searchRef.starBtnShownが0とか1とかで判定すればいいと思うのですが まぁ普通 true か false ですね。 > いつもだと、:の右側のデータを取ってくるので、どうやって左側を受け取るのか、(あるいは、ユーザー IDでデータを取って来て、null、もしくはundefinedだったらスター済みじゃなく、trueだったらスター済みのように判定するのか。) さっきの star するときにも後者のやり方だったので、そちらでいいのではないでしょうか。
MOTOMUR

2017/10/28 14:10

<div> { this.state.searchReference === '0' ? <div><h>here is search reference view</h></div> :<ul>{ this.state.search_users.map( function(searchRef){ return( <div className='col s12 m4'> <Card> <CardTitle title={searchRef.user} subtitle={searchRef.title} /> <CardText> {searchRef.explain} </CardText> <CardActions> { function(){ const self = this if(searchRef.userStar.stars[self.state.userId] == true){ self.setState({starBtnShown : false}) }else{ self.setState({starBtnShown : true}) } return( this.state.starBtnShown == true ?<FlatButton label="STAR" onClick={() => this.starBtnClick(searchRef.uid)} /> :<FlatButton backgroundColor="#a4c639" hoverColor="#8AA62F" label="STARRED" onClick={() => this.starBtnClick(searchRef.uid)} /> )} } </CardActions> </Card> </div> こうして見たのですが、全ての投稿からボタンが消えてしまいました。どこがおかしいでしょうか。 また、この条件分岐は果たしてうまくかけてるでしょうか。
karamarimo

2017/10/28 14:58

いや... starBtnShown を state に入れる必要がないですよね。 しかもすべてのユーザーで starBtnShown という共通の state を使用してますが...。 さらに、 <CardActions>{ function(){ ... } }</CardActions> となってますが、関数を作っただけで実行していません。
MOTOMUR

2017/10/28 15:10 編集

ごめんなさい。<>このようなタグの中で、関数を実行するにはどうしたら良いでしょうか。 編集: <any></any>の間で、 stateの件そうですよね。バカでした申し訳ない。
karamarimo

2017/10/28 15:14

いまの場合関数を使わなくとも <CardActions> { searchRef.userStar.stars[self.state.userId] ?<FlatButton ... /> :<FlatButton ... /> } </CardActions> でいいのではないですか?
MOTOMUR

2017/10/28 15:20

ありがとうございます。 しかしどうやら別の問題があるようです。 searchRef.userStar.stars[self.state.userId] は Unhandled Rejection (TypeError): Cannot read property 'tkI5FZ6gizduCcdsIUwY1iObxUX2' of undefined これによりダメみたいです。 どうしたらいいんでしょうか。
karamarimo

2017/10/28 15:22

searchRef.userStar.stars が undefined、つまり searchRef.userStar が stars というプロパティを持っていないということです。
MOTOMUR

2017/10/28 15:26

なるほど。スターされたことがないユーザーはstarsプロパティを持ってないのも存在してしまうんですよね。 どうやって解消するのがいいでしょうか。
karamarimo

2017/10/28 15:35

stars プロパティが存在しかつ自分のidが含まれるときに、"STARRED"と表示すればいいのではないでしょうか。 (searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId]) ?<FlatButton label="STARRED" ... /> :<FlatButton label="STAR" ... /> }
MOTOMUR

2017/10/28 15:39

ありがとうございます!!!!できました!!!
MOTOMUR

2017/10/28 15:42

リアルタイムに反応するようにしたいところですが、導入が大変そうですね。 次はポストのスター機能を同様につけてゆき、コメント機能を作成していきます。
MOTOMUR

2017/10/29 15:02

作成中デバックをしていたのですが、やっぱり、STARボタンを押したら、リアルタイムでSTARREDとならないとおかしいことに気づきました。どうしたらいいでしょう。
karamarimo

2017/10/29 15:32

https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction ここを見ると分かるように、.transaction() は Promise を返し、その resolve 値はオブジェクトで、snapshot プロパティに更新された snapshot をもつので、こうすればいいんじゃないでしょうか。 }).then(function () { if (add) { return ....transaction(...) } else { ... } }).then(function (result) { if (result.snapshot) { const updatedUser = snapshot.val() // state を更新 } }
MOTOMUR

2017/10/30 06:54

ありがとうございます。stateを更新して、それで判定すればいいと思うんですけど、 その場合、現在のボタン判定の式 (searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId]) をstateでの判定の式に取り替えたらいいんでしょうか? 現在のコードをこの次にあげます。
MOTOMUR

2017/10/30 06:57

starBtnClick(value){ this.addStar(value) } addStar(uid) { const myUserId = this.state.userId const starUserData = uid var add = true firebase.database().ref('users/' + uid + '/userStar').transaction(function(count) { if(count){ if(count.stars&&count.stars[myUserId]){ count.starCount--; count.stars[myUserId] = null add = false }else{ count.starCount++ if (!count.stars) { count.stars = {}; } count.stars[myUserId] = true add = true } } return count }).then(function (result) { if (result.snapshot) { const updatedUser = snapshot.val() //state updates self.setState({starBtnShown:updatedUser}) } }.then(function(){ if(add){ return firebase.database().ref('/users/' + myUserId + '/starred_users/').push().set({ starUserData }) }else{ return firebase.database().ref('/users/' + myUserId + '/starred_users/').orderByChild('starUserData').equalTo(starUserData).once("value", function (snapshot) { snapshot.forEach(function (child) { child.ref.remove() }) }) } }) }
MOTOMUR

2017/10/30 06:58

this.state.search_users.map( function(searchRef){ const self = this return( <div className='col s12 m4'> <Card> <CardHeader title={searchRef.user} subtitle={" star:(" + searchRef.userStar.starCount + ")"} avatar="" /> <CardTitle title={searchRef.title} /> <CardText> {searchRef.explain} </CardText> <CardActions> { (searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId]) ?<FlatButton backgroundColor="#FDD835" hoverColor="#8AA62F" label="STARRED" onClick={() => this.starBtnClick(searchRef.uid)} /> :<FlatButton label="STAR" labelColor="#FFFB3B" backgroundColor="#EOEOEO" onClick={() => this.starBtnClick(searchRef.uid)} /> } </CardActions> </Card> </div>
MOTOMUR

2017/10/30 06:58

this.state.search_users.map( function(searchRef){ const self = this return( <div className='col s12 m4'> <Card> <CardHeader title={searchRef.user} subtitle={" star:(" + searchRef.userStar.starCount + ")"} avatar="" /> <CardTitle title={searchRef.title} /> <CardText> {searchRef.explain} </CardText> <CardActions> { (searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId]) ?<FlatButton backgroundColor="#FDD835" hoverColor="#8AA62F" label="STARRED" onClick={() => this.starBtnClick(searchRef.uid)} /> :<FlatButton label="STAR" labelColor="#FFFB3B" backgroundColor="#EOEOEO" onClick={() => this.starBtnClick(searchRef.uid)} /> } </CardActions> </Card> </div>
MOTOMUR

2017/10/30 07:01

それと、この状態ではsnapshotがnotdefinedとなるようです。
karamarimo

2017/10/30 07:15

どこの snapshot のことでしょうか?
karamarimo

2017/10/30 07:24 編集

最後の then の前に ")" が抜けてますよ。
MOTOMUR

2017/10/30 12:17

thenの )了解です。修正済みです。 result.snapshotの部分です。
MOTOMUR

2017/10/30 12:34

それに加えて現在、ユーザーのポストをクリックしたら、その、ユーザーページに飛ぶように機能させたいのですが、とりあえずそのビューのuserPage.jsを作っています。 この時、ポストのユーザーIDを参照して、ページに飛ぶといいと思うのですが、この場合、urlの管理はuserpage/uid/として表示したほうがいいですよね?その場合は、ルーティングをどう行ったらいいでしょうか。 また、そのユーザーのIDを受け取って、userPage.jsを開いたらいいと思うのですが、どう受け取らせたら良いでしょうか。ファイル間の受け渡しかたがわかりません。
karamarimo

2017/10/30 14:29

あっすいません。凡ミスです。 const updatedUser = snapshot.val() ではなく const updatedUser = result.snapshot.val() でした。これで直りますか?
MOTOMUR

2017/10/30 14:36

コンパイル通るようになりました。 ボタンの表示の判定はどうしたらいいでしょう。試しに、 { (this.state.starBtnShown) ?<FlatButton backgroundColor="#FDD835" hoverColor="#8AA62F" label="STARRED" onClick={() => this.starBtnClick(searchRef.uid)} /> :<FlatButton label="STAR" textColor="#FFFB3B" backgroundColor="#EOEOEO" onClick={() => this.starBtnClick(searchRef.uid)} /> } こうしてしまったら、一回押すと全てがSTARREDになってしまいます。 元の、 searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId] これも使うのでしょうか。 うまくいかないです。
karamarimo

2017/10/30 14:38

> その場合は、ルーティングをどう行ったらいいでしょうか。 また、そのユーザーのIDを受け取って、userPage.jsを開いたらいいと思うのですが、どう受け取らせたら良いでしょうか。ファイル間の受け渡しかたがわかりません。 ファイル間というよりcomponentへの渡し方ですね。 react-routerの機能でurlパラメーターを渡すというのがあります。 https://reacttraining.com/react-router/web/api/Route/component たとえば <Route path="/book/:id" component={Book}/> とすれば、Book コンポーネントに":id"の部分のパラメーターが渡され、this.props.match.params.id で取り出せます。 公式の例 https://reacttraining.com/react-router/web/example/url-params
karamarimo

2017/10/30 14:43

> 一回押すと全てがSTARREDになってしまいます。 それは this.state.starBtnShown を全部のユーザーで共有してるからですね。 先程の result.snapshot.val() がなんの値か理解されていますか? それを state の適切な場所に入れればいいんですが...。
MOTOMUR

2017/10/30 15:00

鵜呑みにしてコピペしていて理解が追いついていませんでした。考え方を整理させていただいてよろしいでしょうか。 最初に、今までの判定通り、 searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId] これで、検索前にstarされていたか否かを判定すると思うので、これは残しますよね。 次に、ボタンを押したら、トランザクションのpromiseのresolveをオブジェクトで受け取ります。この中身をresult.snapshot.val()をコンソールで確認すると、starCount : 0というデータが入っています。 これをどのように使って判定したらいいでしょうか。 そしてstarBtnShownを共有してしまっていたようなのですが、配列型の定義をした方が良かったでしょうか。そうでないのなら、繰り返しのfunction(searchRef)内のstateをどのように一つずつ区別したらよろしいでしょうか。
MOTOMUR

2017/10/30 15:00

ルーティングの件: ありがとうございます。その通り、作成してみます。
MOTOMUR

2017/10/30 15:04

ところで、これは全然機能に問題のないwarningなのですが、material uiのtextFieldをマルチラインに対応させようと、 multiLine={true} この1行を追加したのですが、 No duplicate props allowed react/jsx-no-duplicate-props このようにwarningされます。 表示はマルチラインになっており、機能には問題ないのですが、consolemに黄色い画面がたくさん増えて気持ち悪いのですが、どうやったら改善できますでしょうか。
karamarimo

2017/10/30 15:23

state はなるべく簡単な構造にしておいたほうがいいです。 result.snapshot.val() が何かというと、transaction を呼び出した ref('users/' + uid + '/userStar') の新しい値です。 現在、 this.state.search_users 内の各 searchRef について、 searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId] が true か false かでボタンの表示が変わります。 そして searchRef.userStar はデータベースの 'users/' + id + '/userStar' から取ってきた値です(確か)。 該当する searchRef の userStar プロパティを新しい値で置き換えればいいと思いませんか? this.state.search_users の中から該当するのを見つけるには、一つ一つ uid をチェックする必要がありますね。
MOTOMUR

2017/10/30 15:56 編集

混乱しているので確認ですが、 searchRef.userStar.stars && searchRef.userStar.stars[self.state.userId] は 検索結果の全てのユーザーの中で、スターされてるユーザー かつ その中に自分のユーザーIDがある。の状態です。 この判定を、新しいstateで置き換えて、同じ意味にする場合、 stateを配列にし、  uid : { userstar : { stars:{ ... : ... } starCount : 1 } } このまま配列に入れていけばいいでしょうか。 そして、このstateを searchRef.this.state.starBtnShown[uid].stars && searchRef.this.state.starBtnShown[uid].stars[self.state.userId] このようにしたら、上の条件式を満たしていて、なおかつボタンに同期的になるでしょうか。
MOTOMUR

2017/10/30 15:54

> No duplicate props allowed react/jsx-no-duplicate-props の件ありがとうございます。 自分でも信じられないのですが、二回同じのを書いていて、それを見つけられませんでした。笑 それともう一つ謎のwarningがあり、 proxyConsole.js:54 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `Home`. See https://fb.me/react-warning-keys for more information. in div (at Home.js:294) in Home (created by Route) in Route (at App.js:56) in Switch (at App.js:55) in div (at App.js:52) in MuiThemeProvider (at App.js:51) in Router (created by BrowserRouter) in BrowserRouter (at App.js:50) in App (at index.js:8) とのことですが、何が悪いかさっぱりわかりません。
karamarimo

2017/10/30 16:04 編集

searchRef.this というのは意味不明ですね...。 私が言いたいのは、this.state.search_users の中で該当するユーザーの userStar プロパティ を新しい値で置き換えれば、あとは render 関数で勝手に判定して表示が更新されますよね、ということです。 state に新しく項目を追加する必要はありません。
MOTOMUR

2017/10/30 16:22

ありがとうございます。ようやく理解できました。 ところでここで基礎知識不足の僕がそうするコードを書こうとすると、 self.setState({search_users.uid.userStar:updatedUser}) このように書いてしまいました。 自分の中では該当プロパティの値を新しい値にしてくれ、とやっているつもりなのですが、できないようですね。 stateの該当する一部を変更するためにはどのようにしたらよろしいでしょうか。また、stateに限らず、配列の該当部の子を今回のように更新するときはどうしたらよろしいでしょうか。
karamarimo

2017/10/30 16:57

ネストしたオブジェクトの深いところを更新するのは、実は割と面倒です。 なぜなら react では this.state を mutate してはいけないからです。 なのでオブジェクトをコピーして、それの一部を更新し、setState で代入する必要があります。 幸いヘルパー関数があるのでそれを使えば楽です。 https://github.com/kolodny/immutability-helper (npm install する必要があります) this.search_users は配列ですから、this.search_users.uid と取り出すことはできません。 どのユーザーが該当のユーザーかをループして調べる必要があります。 調べた結果インデックスが i になったとすれば、次のようにすればたぶん更新できます。 const newSearchUsers = update(this.state.search_users, { [i]: {userStar: {$set: someNewValue} } }); this.setState({search_users: newSearchUsers}); {[i]: ...} という記法をご存じないのでしたらこちらを参考にしてください。 https://qiita.com/kmagai/items/95481a3b9fd97e4616c9
MOTOMUR

2017/10/31 02:01

ありがとうございます。ループして更新する操作は記述できたと思います。 then(function (result) { if (result.snapshot) { const updatedUser = result.snapshot.val() for(var i = 0 ; i<this.state.search_users.length ; i++){ if(i==uid){ const newSearchUsers = update(this.state.search_users, { [i]: {userStar: {$set: updatedUser} } }) this.setState({search_users: newSearchUsers}) } } } }) immutabillity-helperをインストールしたのですが、updateがundefinedとなってしまいました。 eslintとかを直す必要があるのでしょうか。ある場合は治しかたを教えていただけると助かります。
MOTOMUR

2017/10/31 03:56

現在返答待ちの間、マイページのコメント表示の機能を作成していました。 考えた通りコーディングしてみたのですが、 <div> { this.state.posts.length === 0 ? <div><h>There is no post</h></div> : <ul>{ this.state.posts.slice().reverse().map( function(post){ const self = this return( <div className='col s12 m4'> <Card> <CardTitle title={post.title} subtitle={post.author+ " star:"+post.starCount} /> <CardText> {post.body} </CardText> <CardActions> <FlatButton primary label="DELETE" onClick={() => self.deleteBtnClick(post.postId)} /> <FlatButton secondary label="Comment" /> </CardActions> <CardHeader showExpandableButton title={"Comments (" + post.postComment.commentCount + ")" }/> <List expandable> <p>--------------------------------------</p> { post.postComment.comments ?<ul>{ post.postComment.comments.slice().map( function(comment){ return( <ListItem leftAvatar={<Avatar src='' />} primaryText={comment.author} secondaryText={comment.body} /> ) } ) }</ul> } </List> </Card> </div> ) }.bind(this) ) }</ul> } </div> このようにすると、中の</ul>のあたりがsyntaxで怒られてしまいます。 直感的にこのように書いてしまったのですが、syntaxエラーを回避するにはどのように書いたらいいでしょうか。
karamarimo

2017/10/31 05:33

> immutabillity-helperをインストールしたのですが、updateがundefinedとなってしまいました。 import update from 'immutability-helper'; を忘れていませんか? > if(i==uid){ i と比較しても意味がないのでは...。 > 中の</ul>のあたりがsyntaxで怒られてしまいます。 見た限りわからないですね。エラーメッセージは何ですか?
MOTOMUR

2017/10/31 05:58

忘れていました!ありがとうございます。 >i と比較しても意味がないのでは...。 自分の感覚だと、iは配列の番地でそれがuidとなっていて、それをiが受け取ってループすると思っていました。 とするとこの場合は、 if(this.state.search_users[i]==uid){ でしょうか・・・・ >見た限りわからないですね。エラーメッセージは何ですか? Syntax error: Unexpected token, expected : (305:29) 303 | ) 304 | }</ul> > 305 | } | ^ 306 | </List> 307 | </Card> 308 | </div>
MOTOMUR

2017/10/31 05:58

矢印がずれていますが、上の行の } これです
karamarimo

2017/10/31 06:33

> 自分の感覚だと、iは配列の番地でそれがuidとなっていて、それをiが受け取ってループすると思っていました。 配列の番地ではありますが、それはユーザーIDとは何の関係もないですよね。 i は単に 0,1,2... と増えていくだけです。 各 search_users[i] に何が入っているか理解されていますか? syntax error ですが、 "post.postComment.comments ? ..." のあとに " : ..." がないからだと思います。
MOTOMUR

2017/10/31 06:44

>"post.postComment.comments ? ..." のあとに " : ..." がないからだと思います。 こちら解決しました。ありがとうございます。 >各 search_users[i] に何が入っているか理解されていますか? スターしているユーザーのユーザーID直下からのデータです。 なので、 if(this.state.search_users[i].uid==uid){ とすればいいでしょうか。IDが格納されていると勘違いしていました。
MOTOMUR

2017/10/31 06:51

おかげさまで、ボタンが同期的に作用するようになりました。 コメント機能やユーザーページを作っていきます。
MOTOMUR

2017/10/31 07:02

ところで、ポスト毎のコメントを参照して、既存の、 <div> { this.state.posts.length === 0 ? <div><h>There is no post</h></div> : <ul>{ this.state.posts.slice().reverse().map( function(post){ const self = this return( <div className='col s12 m4'> <Card style={{ width: '50%', margin: 'auto', }} > <CardTitle title={post.title} subtitle={post.author+ " star:"+post.starCount} /> <CardText> {post.body} </CardText> <CardActions> <FlatButton primary label="DELETE" onClick={() => self.deleteBtnClick(post.postId)} /> <FlatButton secondary label="Comment" onClick={() => self.commentBtnClick(post.postId)} /> </CardActions> <CardHeader showExpandableButton title={"Comments (" + post.postComment.commentCount + ")" }/> <List expandable> <p>--------------------------------------</p> { post.postComment.comments ?<ul>{ post.postComment.comments.slice().map( function(comment){ return( <ListItem leftAvatar={<Avatar src='' />} primaryText={comment.author} secondaryText={comment.body} /> ) } ) }</ul> :<div>there is no comments</div> } </List> </Card> </div> ) }.bind(this) ) }</ul> } </div> このループ内で参照してしまいたいのですが、どうしたら良いでしょう。 ポストのJSONのネストは user_posts:{ 自動生成キー(各ポスト):{ postComment:{ commentCount:0, comments:{ 自動生成キー(各コメント):{ uid: ~~ author: ~~ commentBody: ~~ }}}} この、複数あるポストの中から、複数のコメントをそれぞれ取り出して行く場合。このまま、さっきのループの中にループを作成して表示させることはできますでしょうか。 もしくはこの fetchPosts(){ const self = this const myUserId = self.state.userId firebase.database().ref('/users/'+myUserId).once('value').then(function(snapshot) { self.setState({ username: snapshot.val().user, title: snapshot.val().title, explain: snapshot.val().explain }); }); var onValueChange = firebase.database().ref('/users/'+myUserId+'/user_posts/').on('value',function(snapshot) { const posts = []; snapshot.forEach(function(childSnapshot){ posts.push(childSnapshot.val()) self.setState({ posts: posts }); }) }); this.setState({off:onValueChange}) } のようにfetchComenntsとして新たに配列を作り、うまく操作する必要があるのでしょうか。
karamarimo

2017/10/31 07:33 編集

> このまま、さっきのループの中にループを作成して表示させることはできますでしょうか。 post は既に取得しているので、当然その中にコメントも含まれていますよね。なのでデータベースから取得する必要が無いわけですから、後者のような作業は必要ないですよね。 ただし render 関数が膨れ上がりそうなので、投稿やコメントをそれぞれ1つのコンポーネントにするなどしてはどうでしょうか。他のページでも使えますし。
MOTOMUR

2017/10/31 07:50

なるほどです。 それぞれコンポーネントにする際に引数等含まれると思いますが、この部分はどのように分割すればよろしいでしょうか。
karamarimo

2017/10/31 07:54

コメントなら1つのコメントを表示するのに必要最小限の props を渡せばいいと思います。
MOTOMUR

2017/10/31 08:16

質問文にコードを更新しましたが例えばこんな感じでしょうか。
MOTOMUR

2017/10/31 08:18

仮にこんな感じだったとして、どのようにこのコンポーネントに因数を渡せばいいでしょうか。 <starredRefCard ここに引数を渡すのですか? />
MOTOMUR

2017/10/31 08:39

class starredRefCard extends Component { constructor(props) { super(props); this.state = {}; } render(){ return( <div className='container' style={{ overflowX: 'scroll', width:'100%' }} > <div style={{ display: 'inline-block', whiteSpace: 'nowrap', }}> { this.props.starredUsers.length === 0 ? <div><h>There is no starred user</h></div> : <ul>{ this.props.starredUsers.slice().map( function(starred_user){ return( <Card style={{ width: '100%', display: 'inline-block', margin:'auto' }} > <CardHeader title={starred_user.author} avatar="" /> <CardTitle title={starred_user.title} subtitle={" star:(" + starred_user.postStar.starCount + ")"} /> <CardText> {starred_user.body} </CardText> <CardActions> <FlatButton secondary label="Comment" onClick={() => this.commentFncOpen(starred_user.uid,starred_user.postId)} /> </CardActions> <CardHeader showExpandableButton title="Comments" /> <List expandable> <p>--------------------------------------</p> <ListItem leftAvatar={<Avatar src='' />} primaryText="Author" secondaryText={ <p>Hello</p> } /> </List> </Card> ) }.bind(this) ) }</ul> } </div> </div> )} } export default starredRefCard このようにし、 <starredRefCard starredUsers = {this.state.starred_users}/> このように呼び出してみたのですが、 表示されません。どこがおかしいでしょうか。
MOTOMUR

2017/10/31 08:52

this.commentFncOpen(starred_user.uid,starred_user.postId) あとこの部分もどうしたらいいんでしょうか。
karamarimo

2017/10/31 08:53

属性はスペースを空けてはいけません。 個人的に、複数の投稿を表示するコンポーネントではなく、各投稿をコンポーネントにしたほうがいいと思います。 あと、コンポーネント名は普通最初大文字です。
karamarimo

2017/10/31 09:06

投稿というかユーザーでしたね。すみません。 this.commentFncOpen は何をする関数でしょうか? UI表示だけに関わるものなら StarredRefCard コンポーネント内に関数を移動してもいいと思いますが。
MOTOMUR

2017/10/31 09:12

コメント入力を表示させる関数です。 ボタンを押されたポストのIDとそのユーザーIDを取ってきて、firebaseのupdateでそのポストにコメントを投稿させる機能を中に入れます。
MOTOMUR

2017/10/31 09:17

<div className='container' style={{ overflowX: 'scroll', width:'100%' }} > <div style={{ display: 'inline-block', whiteSpace: 'nowrap', }}> { this.state.starred_users.length === 0 ? <div><h>There is no starred user</h></div> : <ul>{ this.state.starred_users.slice().map( function(starred_user){ return( <StarredRefCard starredUser={starred_user} /> ) }.bind(this) ) }</ul> } </div> </div> このように呼び出し、 class StarredRefCard extends Component { constructor(props) { super(props); this.state = { }; } render(){ return( <Card style={{ width: '100%', display: 'inline-block', margin:'auto' }} > <CardHeader title={this.props.starredUser.author} avatar="" /> <CardTitle title={this.props.starredUser.title} subtitle={" star:(" + this.props.starredUser.postStar.starCount + ")"} /> <CardText> {this.props.starredUser.body} </CardText> <CardActions> <FlatButton secondary label="Comment" onClick={() => this.commentFncOpen(this.props.starredUser.uid,this.props.starredUser.postId)} /> </CardActions> <CardHeader showExpandableButton title="Comments" /> <List expandable> <p>--------------------------------------</p> <ListItem leftAvatar={<Avatar src='' />} primaryText="Author" secondaryText={ <p>Hello</p> } /> </List> </Card> )} } export default StarredRefCard このようにしたのですが、 TypeError: Cannot read property 'starCount' of undefined と言われます。 どこがおかしいですかね。
karamarimo

2017/10/31 09:24

this.props.starredUser.postStar.starCount postStar プロパティってありましたっけ?すみません私はよく覚えてないですが。
MOTOMUR

2017/10/31 09:26

質問文にデータベースを載せますが、一応あると思います。
karamarimo

2017/10/31 09:43

StarredRefCard ってユーザーをpropsとして受け取って表示するコンポーネントですよね? しかし render 関数をみると post として扱っていませんか...?
MOTOMUR

2017/10/31 10:08 編集

勘違いをしていました。機能するようになりました。 >投稿というかユーザーでしたね。すみません。 this.commentFncOpen は何をする関数でしょうか? UI表示だけに関わるものなら StarredRefCard コンポーネント内に関数を移動してもいいと思いますが。 uiの表示に関わるだけなのですが、中の引数をhome内で使いたいです。 ボタンを押すとコメントフォームの表示、非表示、そして押されたポストのIDとそのユーザーIDを取得してしまいたいのです。 その場合はどうやってhomeに持ってきたらいいでしょうか。
MOTOMUR

2017/10/31 12:01

質問文更新しました。  ドキュメントとサンドボックス拝見中です。
karamarimo

2017/10/31 12:53

コメント入力フォームを表示するときには別にHomeにデータを送る必要はないですよね? 送信するときだけ渡せばいいのでは?
MOTOMUR

2017/10/31 13:53

コメントフォームを class CommentForm extends Component { constructor(props) { super(props); this.state = { comment:'', }; } handleChange = (event) =>{ this.setState({comment:event.target.value}) } handleCommentSubmit(this.props.uid,this.props.postId,this.props.myuid,this.props.author){ const self = this var newCommentKey = firebase.database().ref().child('comments').push().key var commentData = { author: self.props.author, uid: self.props.myuid, body: self.state.comment, commentId: newCommentKey, } return firebase.database().ref().update(updates); } firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { count.commentCount++ if (!count.comments) { count.comments = {}; } var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData } } render(){ return( <Card style={{ width: '50%', margin: 'auto', }} > <CardText style={{ width: '35%', margin: 'auto', }} > <TextField id="commentForm" floatingLabelText="type comment" value={this.state.comment} onChange={this.handleChange} /> <RaisedButton label="Comment" secondary={true} multiLine={true} rows={5} style={{ border: '1px solid #EEEEEE', }} onClick={this.handleCommentSubmit} /> </CardText> </Card> ) } このようにして、home内で <div> { this.state.commentForm ?<CommentForm uid={this.state.commentUid} postId={this.state.commentPostId} myuid={this.state.userId} author={this.state.author}/> :<br></br> }.bind(this) </div> 呼び出してみたのですが、 this.state.commentForm ここが、 Syntax error: this is a reserved word このようになります。どのようにしたら治るでしょうか。 また、このような今回の分け方はよろしくなかったら改善方法お願いします。
karamarimo

2017/10/31 14:01

{ this.state.commentForm ?<CommentForm uid={this.state.commentUid} postId={this.state.commentPostId} myuid={this.state.userId} author={this.state.author}/> :<br></br> }.bind(this) 最後の .bind(this) が原因ではないでしょうか。
MOTOMUR

2017/10/31 14:09

>最後の .bind(this) が原因ではないでしょうか。 なしでもありでも、同じように怒られてしまいます。
karamarimo

2017/10/31 14:16

とりあえずそれは文法的に間違っているので消しといてください。 this.state.commentForm の名前を変えてみてください。
MOTOMUR

2017/10/31 14:33

名前をcommentFormShownとしましたが、未だに怒られています。 ちなみに初期化はこんな感じです。 this.state={ commentFormShown:false }
karamarimo

2017/10/31 14:55

{ this.state.commentForm ?<CommentForm uid={this.state.commentUid} postId={this.state.commentPostId} myuid={this.state.userId} author={this.state.author}/> :<br></br> } の周辺のコードも載せてもらえますか?
MOTOMUR

2017/10/31 15:12

<div className='container' style={{ overflowX: 'scroll', }} > <div style={{ display: 'inline-block', whiteSpace: 'nowrap', }}> { this.state.starred_posts.length === 0 ? <div><h>There is no starred user</h></div> : <ul>{ this.state.starred_posts.slice().map( function(starred_post){ return( <StarredRefCard starredPost={starred_post} onCommentFncOpen={this.commentFncOpen.bind(this)}/> ) } ) <li> { this.state.commentFormShown ?<CommentForm uid={this.state.commentUid} postId={this.state.commentPostId} myuid={this.state.userId} author={this.state.author}/> :<br></br> } </li> }</ul> } </div> </div> 周辺のコードです。
karamarimo

2017/10/31 15:55

<ul> { this......map(...) <li> { ... } </li> } </ul> となっているからですね。 <ul> { this......map(...) } <li> { ... } </li> </ul> とすれば直りますが、同じ <ul> 内に両方入れるのは良くないのでは?
MOTOMUR

2017/11/01 01:38

コメントを書きたいポスト直下にコメントフォームを出したいんですよね。半ば強引だなぁと思いながら<ul>内に突っ込みました。 デザインとして、コメントフォームをポップアップというか、画面最上層に出現させるかポスト直下で悩み、 今の知識でできそうなポスト直下としましたが、 最上層にフォームを出現させるにはどうしたらいいでしょうか。
MOTOMUR

2017/11/01 01:58

すいませんそれと、 <CommentForm postUid={this.state.commentUid} postId={this.state.commentPostId} myuid={this.state.userId} author={this.state.author}/> このように呼び出して、 handleCommentSubmit(this.props.uid,this.props.postId,this.props.author,this.props.myuid){ const self = this var newCommentKey = firebase.database().ref().child('comments').push().key var commentData = { author: self.props.author, uid: self.props.myuid, body: self.state.comment, commentId: newCommentKey, } firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { count.commentCount++ if (!count.comments) { count.comments = {}; var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData }else{ var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData } }) } ここの引数として使っているのですが、使い方を間違っているのでしょうか。 Syntax error: Unexpected token となります
karamarimo

2017/11/01 03:53

関数定義を理解されていないのでしょうか? handleSubmit 関数を定義しようとして、引数に渡したい値を書いていますが...。 > 最上層にフォームを出現させるにはどうしたらいいでしょうか。 material-ui にそういうのがありますよ。 http://www.material-ui.com/#/components/dialog
MOTOMUR

2017/11/01 04:06

定義の時に引数いりませんでしたね。。。。 アホで申し訳ないです。。。 ありがとうございます!やってみます!
MOTOMUR

2017/11/01 04:14

ごめんなさい度々、同じようなsyntax errorなのですが、 render(){ return( { this.state.postComments.length === 0 ?<div>there is no comment</div> :<ul>{ this.state.postComments.slice().map( function(comment){ return( <ListItem leftAvatar={<Avatar src='' />} primaryText={comment.author} secondaryText={comment.body} /> ) } ) }</ul> } ) } } export default CommentForm この、this.state.postComments.length === 0 が Syntax error: this is a reserved word となります。
karamarimo

2017/11/01 04:47

return ({ this... === 0 : ### ? ###}) は文法的にまずいですね。なぜだかわかりますか? js のオブジェクトリテラルの { } と jsx のテンプレートに埋め込むための { } を混同してはいけません。 この場合だと前者だと認識されますが、当然 { key: value } の形になってないので失敗します。
MOTOMUR

2017/11/01 04:51

<div>で囲うことで復活しました。return(の後に { を置くのはまずいのですね。
karamarimo

2017/11/01 05:01

というより、埋め込みのための { } はテンプレート内でしか使えないということです。 <div>{ someExpression }</div> は良くても { someExpression } はダメ(というか意味が変わる)です。
MOTOMUR

2017/11/01 05:15

ありがとうございます。 だいぶ完成してきたのですが、関数構造の理解が浅いために、もう一つうまくいかないことがあります。 <StarredRefCard starredPost={starred_post} onCommentFncOpen={this.commentFncOpen.bind(this)}/> この呼び出しのcommentFncOpenには、このonCommentFncOpenでの値を持ってきたく、 StarredRefCardでは onClick={() => this.props.onCommentFncOpen(this.props.starredPost.uid,this.props.starredPost.postId)} としていて、 commentFncOpen(uid,postId){ this.setState({ open:true, commentUid:uid, commentPostId:postId }) } このように関数は定義しています。 このようにStarredRefより、値を持ってきて、 Home内でその値を用いた関数を呼び出すにはどうしたら良いでしょうか。 構造的に問題あれば、構造を変更したいと思います。
karamarimo

2017/11/01 06:10

すみません混乱してきました。 それぞれのコードはどこに書かれているものですか? それぞれの component を確認したいので github に push していただけますでしょうか。 また、StarredRefCard から Home に渡したいというデータは何でしょうか?
MOTOMUR

2017/11/01 06:26

githubに送りました。 >また、StarredRefCard から Home に渡したいというデータは何でしょうか? スターしている人の中の、コメントボタンを押されたポストのIDと、そのポストのユーザーIDです。
karamarimo

2017/11/01 06:38

なるほど、Homeで StarredRefCard と CommentForm を両方管理しようとしているんですね。 CommentForm を StarredRefCard の子にしたほうがいいと思います。 そうすれば uid なども props で渡せます。
MOTOMUR

2017/11/01 06:58

とりあえずそのように構成して、コンパイルは通るようになったので、githubにpushしました。 さて、次に問題なんですが、スターユーザーのポストのコメントボタンをクリックしても、ダイアログが表示できません。 どうしたらいいでしょうか。
karamarimo

2017/11/01 07:05 編集

間違えました。すみません。
MOTOMUR

2017/11/01 07:07

すいません、自分で試行錯誤したら、コメントフォーム表示できました。 コメントフォームのデバックはこれから行いますが、表示できるのでgit push しておきます。
MOTOMUR

2017/11/01 07:11

むしろ今困っているのは、CommentForm.js:25の author: self.props.authorが TypeError: Cannot read property 'author' of undefined なことが直せません。
karamarimo

2017/11/01 07:33

そこで const self = this とする必要はないですね。 handleCommentSubmit: (event) => { ... } としてください(eventは使わないのでいりませんが)。
MOTOMUR

2017/11/01 08:06 編集

ありがとうございます。治りました。 ところで、今度はfirebaseの機能がダメでした。 TypeError: Cannot read property 'commentCount' of null この部分だそうです、count.commentCount++ 周りのコードはこんな感じです。 CommentForm.js handleCommentSubmit = () =>{ var newCommentKey = firebase.database().ref().child('comments').push().key var commentData = { author: this.props.author, uid: this.props.myuid, body: this.state.comment, commentId: newCommentKey, } firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { count.commentCount++ if (!count.comments) { count.comments = {}; var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData }else{ var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData } }) } わかりづらいと思うので、gitにpushしておきます。
MOTOMUR

2017/11/01 08:22

少し修正したのでgit push します。
karamarimo

2017/11/01 08:32

commentCount の初期値は最初0に設定されていますか? されていないなら、commentCount プロパティの存在を判定して処理する必要がありますね。
MOTOMUR

2017/11/01 08:37

質問文の写真にデータ構造を載せてると思いますが、マイページの投稿時のデフォルトで0を入れてあるので、そのエラーではないと思うんですよね。
karamarimo

2017/11/01 09:03

では this.props.uid と this.props.postId はちゃんと取得できていますか?
MOTOMUR

2017/11/01 09:14

どうやらできてます。
MOTOMUR

2017/11/01 10:51

一体何がダメなんでしょうね、。。。。。
karamarimo

2017/11/01 11:14

うーんわからないですね。あとは this.props.uid と this.props.postId がデータベース上に存在する値かどうか確認するぐらいでしょうか。
MOTOMUR

2017/11/01 11:35

試行錯誤した結果、トランザクションの使い方に問題があったようです。無事、commentCountは昨日するようになりました。 しかし、commentsに情報を送るところで困っています。 handleCommentSubmit = () =>{ const self = this firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { if(count){ count.commentCount++ } return count }).then(function(){ self.postNewComment }) } postNewComment = () => { var newCommentKey = firebase.database().ref().child('comments').push().key var commentData = { author: this.props.author, uid: this.props.myuid, body: this.state.comment, commentId: newCommentKey, } var updates = {} updates['/users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/comments/' + newCommentKey] = commentData return firebase.database().ref().update(updates); } 多分、commentsが存在しない状況だからうまく機能しないんだと思うのですが、 commentsがないときはどのように機能させればいいでしょうか。
MOTOMUR

2017/11/01 11:55

このように判定してみましたが、 handleCommentSubmit = () =>{ const self = this firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { if(count){ count.commentCount++ if (!count.comments) { count.comments = {}; self.postNewComment() }else{ self.postNewComment() } } return count }) } このように、 Error: Reference.update failed: First argument contains undefined in property 'users.GA7RVSwlugPeMIgbb1JWV16ISrN2.user_posts.-KxkI_axPxOArm0eT7qP.postComment.comments.-KxrQd2D2sBhh5jGLDHW.author' こうなってしまうんですよね。 どうやったらうまくいくでしょう。。。
MOTOMUR

2017/11/01 12:03

それとこれは全く別の、reactの基礎だと思うんですが、commentFormのコメントボタンが押された時に、PostRef(コメントフォームにとっては親)のステートを書き換えたいです。(open:false) これってどうやったら可能ですか?
karamarimo

2017/11/01 12:13

トランザクション中に別にデータベースにアクセスするのは良くないと思うので、こうすればいいと思います。 1. /comments/ 下に push してキーを生成 2. トランザクションで .../postComment/ にて commentCount を増やし、comments にコメントを追加 > PostRef(コメントフォームにとっては親)のステートを書き換えたいです 先程のコメントにあったこれらを参考にしてください。 https://reactjs.org/docs/lifting-state-up.html https://codesandbox.io/s/wy6z25qnkl
MOTOMUR

2017/11/01 12:30

handleCommentSubmit = () =>{ const self = this var newCommentKey = firebase.database().ref().child('comments').push().key var commentData = { author: this.props.author, uid: this.props.myuid, body: this.state.comment, commentId: newCommentKey, } firebase.database().ref('users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/').transaction(function(count) { if(count){ count.commentCount++ if (!count.comments) { count.comments = {}; var updates = {} updates['/users/' + self.props.uid + '/user_posts/' + self.props.postId + '/postComment/comments/' + newCommentKey] = commentData return firebase.database().ref().update(updates) }else{ var updates = {} updates['/users/' + self.props.uid + '/user_posts/' + self.props.postId + '/postComment/comments/' + newCommentKey] = commentData return firebase.database().ref().update(updates) } } return count }) this.setState({open:false}) } このようにしてみましたが、commentCountしか増えません。 >1. /comments/ 下に push してキーを生成  2. トランザクションで .../postComment/ にて commentCount を増やし、comments にコメントを追加 直感的にコードを書きましたが、これらが実現できていないのですね。どう修正したら良いでしょうか。 >投稿やコメントの実体はデータベースのトップレベルの comments や posts に置いたほうがいいと思います。 実体を置いたとして、そのデータをfetchするには、ユーザーの中にどのようなデータを置き、refすれば良いのでしょうか。。
karamarimo

2017/11/01 13:28

> 実体を置いたとして、そのデータをfetchするには、ユーザーの中にどのようなデータを置き、refすれば良いのでしょうか。。 https://firebase.google.com/docs/database/web/structure-data#flatten_data_structures ここに書いてある通り、例えば /users/JFfjoew/user_posts/ には { JogfeUIfEM: true, KclsllE: true, ... } と言うかたちで、投稿のidのみを格納します。 ユーザーの投稿を取得する際には /users/JFfjoew/user_posts/ からポストidの集合を取得し、さらに各idについて /posts/id から本体を取得します。 > このようにしてみましたが、commentCountしか増えません。 ほぼ同じ場所で transaction を使っているのでさらに update を使う必要はありません。 count は 'users/' + this.props.uid + '/user_posts/' + this.props.postId + '/postComment/' におけるデータです。それを更新すればいいんです。 上記のやり方なら、count.comments[newCommentKey] = true とすればいいです。
MOTOMUR

2017/11/01 14:54

>ユーザーの投稿を取得する際には /users/JFfjoew/user_posts/ からポストidの集合を取得し、さらに各idについて /posts/id から本体を取得します。 そのように変更中なのですが、MyPageにて、投稿機能を更新し、表示も更新中なのですが、まず、 投稿機能をこう書きました。 handleSubmit = (event) =>{ const self = this function writeNewPost() { // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // A post entry. var postData = { author: self.state.username, uid: self.state.userId, body: self.state.post_body, title: self.state.post_title, postId: newPostKey, postStar: { starCount:0, stars:{} }, postComment:{ commentCount : 0, comments:{} }, }; var postIdData ={[newPostKey]:true} // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {} updates['/posts/' + newPostKey] = postData updates['users/' + self.state.userId + '/user_posts/']= postIdData return firebase.database().ref().update(updates); } writeNewPost() this.setState({ post_title:'', post_body:'' }); }; すると、user_postsにはidとtrueの情報が追加され、postsには投稿が更新されたのですが、その、authorの部分が空白となってしまいます。これがバグ❎の一つ目です。 次は、投稿refの機能です。 fetchPosts(){ const self = this const myUserId = self.state.userId firebase.database().ref('/users/' + myUserId).once('value').then(function(snapshot) { self.setState({ username: snapshot.val().user, title: snapshot.val().title, explain: snapshot.val().explain }) }).then(function(){ var onValueChange1 = firebase.database().ref('/posts/').on('value',function(snapshot){ for(var i = 0; i < self.state.postId.length; i++){ } }) this.setState({off1:onValueChange1}) }) } fetchPostsId(){ const self = this const myUserId = self.state.userId var onValueChange = firebase.database().ref('/users/'+myUserId+'/user_posts/').on('value',function(snapshot) { const postsId = [] snapshot.forEach(function(childSnapshot){ postsId.push(childSnapshot.val()) self.setState({ postsId: postsId }); }) }) this.setState({off:onValueChange},self.fetchPosts) } このように定義し、 componentDidMount(){ const self=this var unsub = firebase.auth().onAuthStateChanged(function(user){ if(user){ self.setState({ userId : firebase.auth().currentUser.uid },self.fetchPostsID); } else { self.props.history.push('/login') } }) this.setState({unsub}); } このように呼び出しているのですが、MyPage For 〜〜の画面や、投稿も、呼び出しているstateの部分が真っ白で何も出てきません。 関数の定義や呼び出し方に不備があると思います。 まだ青い知識で、どこが違うのかわからないので、教えてもらえると助かります。
karamarimo

2017/11/01 15:28

> authorの部分が空白となってしまいます authorの部分だけが null になるのでしょうか?それは不思議ですね....。 > MyPage For 〜〜の画面や、投稿も、呼び出しているstateの部分が真っ白で何も出てきません。 onAuthStateChanged でセットしたリスナーはログイン状態が変化したときのみ呼び出されます。 このコンポーネントが表示されてからログイン状態が変化するというのは、ページをリロードしたときぐらいです(たぶん)。 なので onAuthStateChanged のリスナーはそのままにして、component表示時に firebase.auth().currentUser の存在をチェックし、あれば uid を取得して fetchPostsID すればいいんじゃないでしょうか。 また fetchPosts と fetchPostsId を分けるメリットがあまりないような気がします。 ref('/users/' + myUserId) の値を取得すれば、それに ref('/users/'+myUserId+'/user_posts/') も含まれているので、2回やる必要はないと思います。 また、fetchPosts ですが setState は promise を返さないのでそのまま .then でつなげることはできません。
MOTOMUR

2017/11/01 15:55

fetchPostsData_Id(){ const self = this const myUserId = self.state.userId var onValueChange = firebase.database().ref('/users/'+myUserId).on('value',function(snapshot) { self.setState({ username: snapshot.val().user, title: snapshot.val().title, explain: snapshot.val().explain }) const postsId = [] snapshot.child('user_posts').forEach(function(childSnapshot){ postsId.push(childSnapshot.val()) }) self.setState({ postsId: postsId },self.fetchPosts); }) this.setState({off:onValueChange}) } fetchPosts(){ const self = this var onValueChange1 = firebase.database().ref('/posts/').on('value',function(snapshot){ for(var i = 0; i < self.state.postId.length; i++){ } }) } このように修正しました。IDを全て取得後にポストのfetchが始まらないと、バグりそうなのでこうしました。なくても同期的に動くのであれば、一つにまとめようと思うのですが!
MOTOMUR

2017/11/01 16:16

先ほどに、表示を追加してみました。 fetchPostsData_Id(){ const self = this const myUserId = self.state.userId var onValueChange = firebase.database().ref('/users/'+myUserId).on('value',function(snapshot) { self.setState({ username: snapshot.val().user, title: snapshot.val().title, explain: snapshot.val().explain }) const postsId = [] snapshot.child('user_posts').forEach(function(childSnapshot){ postsId.push(childSnapshot.key) }) self.setState({ postsId: postsId },self.fetchPosts); }) this.setState({off:onValueChange}) } fetchPosts(){ const self = this var onValueChange1 = firebase.database().ref('/posts/').on('value',function(snapshot){ const posts = [] for(var i = 0; i < self.state.postsId.length; i++){ posts.push(snapshot.val()[self.state.postsId[i]]) self.setState({posts: posts}) } }) this.setState({off1:onValueChange1}) } この状態だと、最後のポストしか表示できないのですが、 const posts = [] for(var i = 0; i < self.state.postsId.length; i++){ posts.push(snapshot.val()[self.state.postsId[i]]) self.setState({posts: posts}) } この辺の扱いをうまくするにはどうしたら良いでしょうか。
MOTOMUR

2017/11/02 02:18

ごめんなさい。別のところが要因でした。先ほどのは改善いたしました。gitに新しいコードをプッシュしてます。 現在。デリートボタンを押した後に、refの中身が減ることによる、エラーが起きています。 どのようにこのエラーを回避したらいいでしょうか。
karamarimo

2017/11/02 03:38

ループする度に setState する理由がないので、外に出したほうがいいです。 fetchPosts 内で ref("/posts") を全部取得していますが、投稿が増えるに従い負担が増えると思うので、 各 postId=self.state.postsId[i] に対して .ref('/posts/' + postId) を取得する、としたほうがいいと思います。 また、その場合 .on ではなく .once としたほうが、Promise も使えるので楽だと思います。 Promise.all を使うとすっきりします。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all Promise.all(this.state.postsId.map(function (id) { return firebase.database().ref('/posts/' + id).once('value').then(function (snapshot) { return snapshot.val() }) })).then(function (posts) { self.setState({ posts }) }) > デリートボタンを押した後に、refの中身が減ることによる、エラーが起きています。 何のエラーですか?
MOTOMUR

2017/11/02 08:03

ありがとうございます。危うく大変なデータ量を通信させるところでした。 >何のエラーですか? ポストを消してしまったことによる、refの中身がなくなり、表示する中身がないよってなっていた・・・・・・・・はずなのですが、 今やってみると、治っていました。一体何のバグだったんでしょうか笑 ありがとうございます。 これからコメント機能のデバックしていきます。
MOTOMUR

2017/11/02 08:21 編集

この部分なんですが。 handleCommentSubmit = (val) =>{ const self = this function writeNewComment(){ var newCommentKey = firebase.database().ref('/comments/').push().key var commentData = { author: self.props.post.author, uid: self.props.post.myuid, body: val, commentId: newCommentKey, } console.log(newCommentKey) var updates = {} updates['/comments/' + newCommentKey] = commentData updates['posts/' + self.props.post.postId + '/postComment/' + '/comments/' + newCommentKey] = true return firebase.database().ref().update(updates); } writeNewComment() firebase.database().ref('posts/' + this.props.post.postId + '/postComment/').transaction(function(count) { if(count){ count.commentCount++ return count } }) this.setState({open:false}) } ここの、 return firebase.database().ref().update(updates); ここが Error: Reference.update failed: First argument contains undefined in property 'comments.-KxvnHTebnyyBVVpLUw5.uid' このように言われるのですが、このキーの後半のuidはどこにもつけてないのに勝手に現れてしまいます。どこがおかしいのでしょうか。 gitもpushしておきました。
karamarimo

2017/11/02 08:28

commentData の uid が undefined ってことじゃないでしょうか。 つまり self.props.post.myuid プロパティが存在しないということです。
MOTOMUR

2017/11/02 08:44

ありがとうございます。内容を勘違いしていました。 コメント投稿機能、ほとんどうまくいくようになったのですが、 commentCountを増やすのがうまくいきません。 handleCommentSubmit = (val) =>{ const self = this function writeNewComment(){ var newCommentKey = firebase.database().ref('/comments/').push().key var commentData = { author: self.props.post.author, uid: self.props.post.uid, body: val, commentId: newCommentKey, } var updates = {} updates['/comments/' + newCommentKey] = commentData updates['posts/' + self.props.post.postId + '/postComment/' + '/comments/' + newCommentKey] = true return firebase.database().ref().update(updates); } writeNewComment() firebase.database().ref('posts/' + this.props.post.postId + '/postComment/').transaction(function(count) { count.commentCount++ }) this.setState({open:false}) } どうしたらうまくいくでしょうか。
karamarimo

2017/11/02 10:38

console.dir か console.table で count の中身を表示してみてはどうですか? writeNewComment() 関数を作っているのは何か理由があるんでしょうか?
MOTOMUR

2017/11/02 13:11

nullって帰ってきますね。そこにcommentCountは存在してるし、postCommentも存在してるのに。 なぜnullなのかは不明なのでアイディアお願いします。 全く理由はないですが、https://firebase.google.com/docs/database/web/read-and-write?hl=ja これを参考に一度作ったため、髪を引かれる感じでこの形で作っています。 呼び出すの面倒臭いので、スマートなコードがあるならば教えてください。
karamarimo

2017/11/02 14:07

> なぜnullなのかは不明なのでアイディアお願いします。 調べてみると、こんな動作をするらしいです。 transaction はローカルにキャッシュが無い場合はまず null を引数の関数に与えて実行し、同時にサーバーにデータをリクエストします。もしデータベースから返ってきたデータが null なら、先の実行結果をデータベースに送ります。そうでなければ、受け取ったデータを関数に与えて実行します。 https://stackoverflow.com/questions/16359496/firebase-transaction-api-call-current-data-is-null なので、null が渡される場合も考慮しなければいけません。 if (count) { ... } else { return {} // どうせこの値は捨てられる } とでもすればいいのではないでしょうか。 > 呼び出すの面倒臭いので、スマートなコードがあるならば教えてください。 単に中のコードを外に出せばいいのでは?
MOTOMUR

2017/11/02 14:42

ありがとうございます。分解して整理してみたところ、マイページのビューまではうまくいったようです。 git push しておきます。
MOTOMUR

2017/11/02 14:50

ところで、これはCSS的な質問になってしまうのですが、 現状の構造だと、コメントリストを開くボタンを押すと、コメントの下にカードの表示が揃ってしまうのです。 そこで、 verticalAlign: 'top', をこのようにカードに入れてみたのですが、 <Card style={{ width:'300px', height:'300px', verticalAlign: 'top', }} > これではカードが上揃いに表示できないのですが、 解決方法をわかったりしますか?
karamarimo

2017/11/02 16:02

> コメントリストを開くボタンを押すと、コメントの下にカードの表示が揃ってしまうのです。 揃うとはどういうことでしょうか? スクショを貼ってもらうと分かりやすいです。
MOTOMUR

2017/11/03 03:13

質問文に貼らせていただきました。
karamarimo

2017/11/03 04:34

一番外側の li に verticalAlign: 'top' をつけないと意味がないと思います。 実際に隣のコメントと並ぶ要素はそれなので。 そもそも高さを指定するのが良くないような気がしますね...。 高さは内側の要素に決めさせればいいのではないでしょうか。 あと ul や li をたくさん使っていらっしゃいますが、別に要らないのでは? 雑にコピペしてデモを作りました。 https://codesandbox.io/s/mj4yn77zvy
MOTOMUR

2017/11/03 05:00

ありがとうございます。色々なことをコンパイル可能にしているうちに混乱してごちゃごちゃになっていました。おかげで治りました。ありがとうございます。 次に、firebaseに画像を送るようにしたいのですが、 https://www.npmjs.com/package/material-ui-upload これを利用したり、 https://qiita.com/deruta1992/items/90d6a995e7bae8b4b5a3 この記事を参考にしてみたりしたのですが、 いまいち、どのようにstorageに写真情報を送り、Realtime Databaseでどのようにその情報を管理したらいいのかわかりません。 考え方の整理のつくようなアドバイスもらえると嬉しいです。
MOTOMUR

2017/11/03 05:16 編集

それに加えて、https://qiita.com/shisama/items/d214ea44056e63252bd5 これを参考に、ユーザーのポスト内にhttp://から始まる文字列を発見したら自動的に、リンク先になるようにしたかったのですが、この例ではどのようにやったら自分のプロジェクトでうまくできるかわかりません。 また、もっと他に使いやすい、このようなプラグインがあれば、おすすめ教えてください。
karamarimo

2017/11/03 06:18

一通り公式ガイドを読んでみてはどうでしょうか。 https://firebase.google.com/docs/storage/web/start (左に7ページくらいあるので軽く目を通してみてください) 手順としては、画像をStorageにアップロードし、ダウンロードURLを取得し、データベースの適当な場所に保存するという感じです。 Storageのアップロード場所は uid で決めればいいんじゃないでしょうか。
MOTOMUR

2017/11/03 07:58

>これなんか楽でいいんじゃないですかね。 ありがとうございます。簡単に導入できました。 >一通り公式ガイドを読んでみてはどうでしょうか。 読んで参りました。 とりあえず、Storageのパス管理についてですが、uid/profile.jpgのように送ればいいですよね。 そこで、わからないのですが、まず、アップロードするフォームをどのようにして作ればいいのかというのがわからないのと(Docsの内容はわかるけどUIの見た目をどうしたらいいかという話です。)、 ダウンロードURLの文字列をデーターベースのImageというパスに入れたとして、 それをいつも通り参照するだけで、画像の表示ができるでしょうか? 教えてください。
karamarimo

2017/11/03 14:28

> Docsの内容はわかるけどUIの見た目をどうしたらいいか UIのデザインはお好みで、としかいいようがないですが...。 先程の material-ui-upload をそのまま使えばいいのでは? > ダウンロードURLの文字列をデーターベースのImageというパスに入れたとして、 それをいつも通り参照するだけで、画像の表示ができるでしょうか? <img>タグのsrcにURLを入れれば表示されますね。
MOTOMUR

2017/11/03 15:06

ありがとうございます。 色々いじってみたのですが、 Uncaught FirebaseStorageError {code_: "storage/invalid-argument", message_: "Firebase Storage: Invalid argument in `put` at index 0: Expected Blob or File.", serverResponse_: null, name_: "FirebaseError"} このように言われてしまいます。 material-ui-upload 、 storage 共に新しいものを使用しているので、色々混乱しています。 どこがおかしいでしょう。 質問文に該当のコードを更新します。
MOTOMUR

2017/11/03 15:11 編集

コードの変更点としては、 元あった、プロフィール情報をrealtime databaseに送るコードを、アップロード完了した時に起動するfunction内に移し、submitボタンを押した後、プロフィール画像をアップロードさせた後に、realtime Database に送るようにしたところです。
karamarimo

2017/11/03 16:46

<UploadPreview> が onChange イベントハンドラーに渡す pictures はたぶん配列なので .put() に直接渡せません。 <UploadPreview> は複数の画像を選択できますが、どのように処理しようと考えているのでしょうか。
MOTOMUR

2017/11/04 02:29

プレビューが見れた方がユーザーはやりやすいと思って<UploadPreview> を選びましたが、 どのように1枚のみという処理にしようかは後で考えようと思っていました。 <Upload> なら配列ではなく処理できるなら、とりあえずはそちらで行ってもいいのですが。 ユーザーが1枚のみアップロードするとして、picturesの配列の1枚目をthis.state.pictures[0]としてみたのですが、{ }に入った配列もこのように処理すると一枚目が表示されるのでしょうか?
MOTOMUR

2017/11/04 02:51

詳しく調べていただき、ありがとうございます。 1枚のアップロードでないと、都合が悪いので、<Upload>で進めています。 onFileLoad = (e, file) => console.log(e.target.result, file.name) この結果は、 data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABA〜中省略〜sXCFaM4hnAqD+ZepH//Z ak-103.jpg このように出てくるのですが、 これをstate pictureに入れて、 var file = this.state.picture var metadata = { contentType: 'image/jpeg' } var uploadTask = firebase.storage().ref().child('profile_images/' + self.state.userId).put(file, metadata); このようにするのは間違っているのでしょうか。firebaseの先ほどと同じエラーが出てしまいます。
karamarimo

2017/11/04 02:55

this.state.picture はどのようにセットしましたか?
MOTOMUR

2017/11/04 03:00 編集

onFileLoad = (e, file) => this.setState({picture:e.target.result}) upLoadFile(){ const self = this var file = this.state.picture var metadata = { contentType: 'image/jpeg' } var uploadTask = firebase.storage().ref().child('profile_images/' + self.state.userId).put(file, metadata); uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, function(snapshot) { // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; console.log('Upload is ' + progress + '% done') switch (snapshot.state) { case firebase.storage.TaskState.PAUSED: console.log('Upload is paused'); break; case firebase.storage.TaskState.RUNNING: console.log('Upload is running'); break; } }, function(error) { // A full list of error codes is available at // https://firebase.google.com/docs/storage/web/handle-errors switch (error.code) { case 'storage/unauthorized': // User doesn't have permission to access the object break; case 'storage/canceled': // User canceled the upload break; case 'storage/unknown': // Unknown error occurred, inspect error.serverResponse break; } }, function() { var downloadURL = uploadTask.snapshot.downloadURL; firebase.database().ref('users/' + this.state.userId).set({ title: this.state.title, user: this.state.username, explain: this.state.explain, uid:this.state.userId, profile_image:downloadURL, userStar:{ starCount:0, stars:{} } }) this.props.history.push('/') window.alert("you can successfully make your page!") }) } こんな感じです。
karamarimo

2017/11/04 03:11

.put() に渡せるのは Blob などで、File は Blob なので渡せます。 たぶん onFileLoad の第2引数は File object なのでそれをセットすればいいと思います。 onFileLoad = (e, file) => this.setState({picture: file})
MOTOMUR

2017/11/04 03:47

ありがとうございます。プロフィール画像をうまく取得し、表示できました。 投稿の写真については試行錯誤してみます。 次にやりたいのは、カードをクリックされたら、そのポストを書いた人のユーザーページに飛ぶようにしたいのですが、カード自体にはクリックされた時の関数がないと思うので、どのようにしてこの機能を 実装したら良いでしょうか。
karamarimo

2017/11/04 04:37 編集

カード全体にクリックイベントハンドラーを設定すると、例えばコメントボタンを押したときにも反応するので、その場合はユーザーページにも飛ばないようにするという処理が必要になります。 個人的には CardHeader (確か CardHeader にユーザー名や写真を表示してましたよね?)をクリックしたときに飛ぶほうがいいと思います。 どちらにせよ、コンポーネントに直接 onClick 属性を設定するとうまくいきます。
MOTOMUR

2017/11/04 04:43

なるほどです。CardHeaderがクリックした時に動く関数onClickみたいなものが見つけられません!どうしたらいいでしょうか! それと、押した時にはself.props.history.push('/userpage/:user')とすればいいんでしょうか? この:userの部分はそのポストのユーザーIDを('/userpage/' + this.props.post.uid) としたらいいんでしょうか。
karamarimo

2017/11/04 04:53

> CardHeaderがクリックした時に動く関数onClickみたいなものが見つけられません!どうしたらいいでしょうか! Material-UIのdocsには書いてない属性を指定すると、内部の一番の外側の要素にそのまま渡されるようです。なので CardHeader に直接 onClick 属性を指定してみてください。 > self.props.history.push('/userpage/:user') ":user"というのは react-router がマッチした url からパラメーターを抽出するための syntax であって、実際にブラウザの url を指定するときに書くものではありません。 当然そのように自分で id を指定する必要があります。
MOTOMUR

2017/11/04 05:25

そのように実装したらできました! ありがとうございます! 次にSignupの機能を、メールアドレスの確認後にアカウントを作れるようにしたいのですが、Docsを読んでもいまいちよくわかりませんでした。 Signupページの構成としては、現在でいう、Signupさせて、メールの確認URLから飛んできてもらって、その後、makemypageに飛ぶようにしたいのですが、このメールの一連の機能がわかりません。どのように構成したらいいでしょうか。 signupページをmaterial ui の steperに乗せていき作る予定なのですが。
karamarimo

2017/11/04 05:43

メールアドレスの確認メールですか...と思って見るとそんな機能が用意されてるんですねぇ。感心しました。 https://firebase.google.com/docs/auth/web/manage-users#send_a_user_a_verification_email しかも確認メールからアプリへのリダイレクトまでできるんですね。 https://firebase.google.com/docs/auth/web/passing-state-in-email-actions ここに書いてある例の通りにやればいいのでは? もちろん iOS とか Android のは要らないと思いますが。
MOTOMUR

2017/11/05 05:34

コーディングを色々している途中なのですが、根本的なwarningじゃなかったので、放置していたものの治し方を知りたいのですが、 <img src={this.props.post.post_image}></img> このタグの扱い方がまずいらしく、 img elements must have an alt prop, either with meaningful text, or an empty string for decorative images と言われるのですが、どこがまずいのでしょうか?
karamarimo

2017/11/05 06:45

そのままですが、img 要素は alt 属性が必要ということです。 画像が読み込めない時に代わりに表示されるテキストです。 装飾のための画像なら empty string "" を指定すればいいと書いてあります。
MOTOMUR

2017/11/05 16:54

ありがとうございます!altが必須だったのですね。 メール認証の機能を頑張って導入しようとしているのですが、うまく行えません。 class Signup extends Component{ constructor(props){ super(props) this.handleNext = this.handleNext.bind(this) this.handlePrev = this.handlePrev.bind(this) this.state={ email: '', password:'', errorCode:'', errorMessage:'', verifyErrorMassage:'', stepIndex: 0, } } getStepContent(stepIndex) { switch (stepIndex) { case 0: return ( <Card style={{ width: '50%', margin: 'auto', }} > <CardTitle title='Signup Form' /> <CardText style={{ width: '35%', margin: 'auto', }} > {this.state.verifyErrorMassage} <TextField id="emailform" value={this.state.email} floatingLabelText="UserID" onChange={this.handleChange_email} /> <br /> <TextField id="passwordform" value={this.state.password} floatingLabelText="password" type='password' onChange={this.handleChange_password} /> <br /> <RaisedButton label="Signup" secondary={true} onClick={this.onSignupBtnClick} /> {this.state.errorCode} {this.state.errorMessage} </CardText> </Card> ); case 1: return ( <p style={{textAlign:'center'}}> please verification your email account! </p> ); case 2: return ( <p> make your page for getstarted! </p> ); } } componentWillMount(){ firebase.auth().onAuthStateChanged(function(user){ if(user){ } else { } }) }; handleChange_email = (event) => { this.setState({ email: event.target.value }); }; handleChange_password = (event) => { this.setState({ password: event.target.value }); }; onSignupBtnClick = (event) => { const self = this firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).catch(function(error) { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; self.setState({ errorCode:errorCode, errorMessage:errorMessage, }) // ... }) var actionCodeSettings = { url: 'https://www.example.com/?email=' + firebase.auth().currentUser.email, iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, handleCodeInApp: true }; firebase.auth().currentUser.sendEmailVerification(actionCodeSettings) .then(function() { self.setState({stepIndex:1}) }) .catch(function(error) { // Error occurred. Inspect error.code. }) } handleVerifyEmail(auth, actionCode, continueUrl) { const self = this // Try to apply the email verification code. auth.applyActionCode(actionCode).then(function(resp) { self.props.history.push('/') }).catch(function(error) { self.setState({ verifyErrorMassage:'your verification is failed' }) }) } handleNext() { const {stepIndex} = this.state; if (stepIndex < 2) { this.setState({stepIndex: stepIndex + 1}); } } handlePrev() { const {stepIndex} = this.state; if (stepIndex > 0) { this.setState({stepIndex: stepIndex - 1}); } } render(){ const {stepIndex} = this.state return( <div style={{marginBottom:'10px'}}> <Card style={{marginBottom:'10px'}}> <Stepper style={{ width:'70%', margin:'auto', }} activeStep={stepIndex} connector={<ArrowForwardIcon />}> <Step> <StepLabel>signup for email account</StepLabel> </Step> <Step> <StepLabel>please verification your email account!!</StepLabel> </Step> <Step> <StepLabel>make your page for getstarted!</StepLabel> </Step> </Stepper> {this.getStepContent(stepIndex)} </Card> </div> )} } とりあえずこんなページでサインアップしてもらって、成功したら2項目に行き、その後メール認証のメールからのリンクで、2番目のステッパーから3番目のステッパーを乗せたmakeMyPageに飛ばそうと思っているのですが、 成功したら2項目にいく部分がうまくいってないですし、 そもそも現在のコードの書き方で、サインアップ成功したらメールを送れるようになっていて、それが成功したら2項目にいく状態なのか不明です。 不具合はどこでしょうか。
karamarimo

2017/11/06 01:42

「2項目にいく部分がうまくいってない」とは具体的に何ができていないのでしょうか? メールアドレス確認メールは届きますか? また、actionCodeSettings を公式ガイドからコピペしてますが、「makeMyPageに飛ばそうと思っている」なら url はその url にする必要がありますし、android iOS handleCodeInApp の項目は不要ではないですか?
MOTOMUR

2017/11/06 03:20 編集

>メールアドレス確認メールは届きますか? 届かなくて困っています。それと、ステップ2に行きません。 >actionCodeSettings を公式ガイドからコピペしてますが、「makeMyPageに飛ばそうと思っている」なら url はその url にする必要がありますし、android iOS handleCodeInApp の項目は不要ではないですか? urlはホスティングした時に、どのようなURLになるか不明なのでそのままなのですが、history.pushで実現できるでしょうか? android iOS handleCodeInApp の件ですが、今後スマホでも操作可能にすることを考慮して、2度コードを探しに行き、コピペする労力がめんどくさそうなので残してあります。ただ、handleCodeInAppに関しては一体何に必要なのかは不明です。
karamarimo

2017/11/06 03:41

createUserWithEmailAndPassword は非同期にアカウントを作るので直後に firebase.auth().currentUser を取得しても null になってしまうのではないかと思います。 createUserWithEmailAndPassword は Promise で User を返すのでそれを使えばいいのではないでしょうか。 https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword > 今後スマホでも操作可能にすることを考慮して webアプリだけでなくiOSアプリや android アプリも作るということでしょうか?そうでないなら必要ないですよ。
MOTOMUR

2017/11/06 04:02

firebase.Promiseの使い方がわかりません firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).catch(function(error) {・・・} .Promise{ var actionCodeSettings = { url: 'https://www.example.com/?email=' + firebase.auth().currentUser.email, iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, handleCodeInApp: true }; firebase.auth().currentUser.sendEmailVerification(actionCodeSettings) .then(function() { self.setState({stepIndex:1}) }) .catch(function(error) { // Error occurred. Inspect error.code. }) } } としてみたのですが。。
karamarimo

2017/11/06 04:18

firebase.Promise は普通の Promise といっしょです。使い方をご存知なければ調べてみてください。 firebase.auth().createUserWithEmailAndPassword(...) .then(function (user) { // send email verification to user.email }, function (err) { // falied to create a user }) user を渡してくれるのでわざわざ firebase.auth().currentUser.email で取得する必要はないですよ。
MOTOMUR

2017/11/06 13:02

ありがとうございます。コードはスッキリしてきてなんだか動きそうなのですが、やはりメールは届きません。メールが送られてくることがとりあえずの目標なので、困っています。 onSignupBtnClick = (event) => { const self = this firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password).then(function(user){ var actionCodeSettings = { url: 'https://www.example.com/?email=' + firebase.auth().currentUser.email, iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, handleCodeInApp: false } firebase.auth().currentUser.sendEmailVerification(actionCodeSettings).then(function() { self.setState({stepIndex:1}) }) }).catch(function(error) { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; self.setState({ errorCode:errorCode, errorMessage:errorMessage, }) // ... }) } どこがダメなのでしょうか?
karamarimo

2017/11/06 13:18

わからないですね...。 とりあえず console.log を入れてどこが実行されていてどこが実行されていないか確かめてみてください。
MOTOMUR

2017/11/06 14:06

どうやら、 user.sendEmailVerification(actionCodeSettings).then(function() { self.setState({stepIndex:1}) console.log("Hello") }) ここまで到達できていないようです。 これより上ではconsole.logが出力されました。 console上でエラーがいくつか発見されました。 POST https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key=AIzaSyABS_UCwqY7Mg0pa7s_yEzEhds8O6cap_c 4 Uncaught O {code: "auth/unauthorized-continue-uri", message: "Domain not whitelisted by project"} code : "auth/unauthorized-continue-uri" message : "Domain not whitelisted by project" __proto__ : Error G : ƒ () constructor : ƒ (a,b) toJSON : ƒ () __proto__ : Object Uncaught Error: The error you provided does not contain a stack trace. at parseError (parser.js:74) at getStackFrames (getStackFrames.js:16) at crashWithFrames (listenToRuntimeErrors.js:23) at listenToRuntimeErrors.js:38 at errorHandler (unhandledError.js:18) どのように直せますでしょうか。
MOTOMUR

2017/11/06 14:32 編集

ごめんなさい。それは具体的にはどの部分の話でしょうか。 コンソールでホワイトリストに追加という作業をすればいいのでしょうか。< ごめんなさい。記事を発見しました。頑張ってみます
MOTOMUR

2017/11/06 14:38

ホワイトリストに登録してあるドメインをactionCodeSettingに入れてみたのですが、うまく行きません。同じエラーが残っています。 どこがダメなのでしょうか。
MOTOMUR

2017/11/06 15:42 編集

ありがとうございます!!!http://から始めないとダメだったんですね。。。例が省略していたので書いていなかったのですが、書いたらできました。。。。 >とりあえずhttp://から始めるのはあってるようです。 しかし、確認メールの反応が鈍く、なかなか届かないのでなかなかデバックできません。 僕の今回のプロジェクトでは、メールの確認が取れた後に飛ぶページに、/?email= の部分は必要でしょうか? http://localhost/makeMyPageに飛んでもらえればいいですよね。 と思ったのですが構造をイマイチ理解していません。 メールのURLを踏むと、認証できました!っていうページに飛びます。そこにボタンがあってそれを押すとどこかに飛ばせるんですが、僕の今の状態だと飛べません。 飛ばずに自分のプロジェクトに戻ってもらってもいいのですが、その場合はどのようにパスをpushしていいのでしょうか。
karamarimo

2017/11/06 15:41

> /?email= の部分は必要でしょうか? あくまでそれは例だと思います。URLのパラメーターをどうするかはweb app次第です。 > メールのURLを踏むと、認証できました!っていうページに飛びます。そこにボタンがあってそれを押すとどこかに飛べるんですが、僕の今の状態だと飛べません。 そのページは firebase が生成したページですか? ボタンを押しても反応がないということでしょうか? エラーはでてないですか?
MOTOMUR

2017/11/06 16:02 編集

>そのページは firebase が生成したページですか? ボタンを押しても反応がないということでしょうか? エラーはでてないですか? firebaseが生成したページです!エラーは出てないです!認証されました!ってなって、ボタン消えて終わりです。 イメージ的には指定したurlに飛んでくれると思ったのですが。 現在localhostでも、メール送信可能か検証中です。これで指定urlに飛んだ場合は報告します。
MOTOMUR

2017/11/07 01:46

localホストだと確認のボタンは反応しないようです。firebaseプロジェクトのurlで指定すると機能します。
MOTOMUR

2017/11/07 02:17 編集

ということなので、firebaseのhosting機能を利用したいのですが、一連のfirebase deployまで行ったのですが、ページが表示されません。 どのようにやったらサーバーが動くでしょうか。 追記:デプロイするときに、どのようにプロジェクトを管理し、どのファイルをデプロイしていいかがわからないです。これが理由で真っ白なWebページになるんだと思います。
MOTOMUR

2017/11/07 08:13 編集

npm build が必要だったのですね。やってみたのですが、 MurakaminoMacBook-Pro:plz_donation owner$ npm run build > resource-center@0.1.0 build /Users/owner/works/plz_donation > react-scripts build Creating an optimized production build... Failed to compile. Template execution failed: ReferenceError: features is not defined npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! resource-center@0.1.0 build: `react-scripts build` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the resource-center@0.1.0 build script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /Users/owner/.npm/_logs/2017-11-07T08_07_04_546Z-debug.log こんな感じでbuildしてくれません。設定が足りないのですかね? それとも軽いwarningを放置しているからなのでしょうか。 Template execution failed: ReferenceError: features is not defined この部分を検索かけてみたのですが、他の該当者がおらず。どのようにしたらいいでしょう。
karamarimo

2017/11/07 08:36

features っという名前はどこで使ってますか?
MOTOMUR

2017/11/07 09:23

firebase init をした際に勝手に更新された、publicのindex.htmlで使われてますね。  gitにある元のindexに書き換えてみます。
MOTOMUR

2017/11/07 09:25

元のindexに直すと、buildされました。 このままfirebase init をやって大丈夫でしょうか? 指定フォルダはbuildでいいですよね?
MOTOMUR

2017/11/07 10:02

一応デプロイしてみたのですが、ホスティングうまくいきましたとは出るんですが、プロジェクト自体には行けません。どうしたらいいのでしょう。
karamarimo

2017/11/07 10:25

> firebase init をした際に勝手に更新された、publicのindex.htmlで使われてますね。  https://firebase.google.com/docs/hosting/quickstart?hl=ja#header_1 ここにある通り、firebase init をするとその時点で index.html がないなら勝手に作られますが、上書きされたんですか? > このままfirebase init をやって大丈夫でしょうか? 指定フォルダはbuildでいいですよね? npm run build すると build フォルダーにビルドされたファイルができると思うんですが、それを確認して指定してください。 > プロジェクト自体には行けません。 というのは、ブラウザでアクセスしても応答がないということでしょうか? ブラウザでエラーなど出てないですか?
MOTOMUR

2017/11/07 10:38

>index.html がないなら勝手に作られますが、上書きされたんですか? 上書きされましたね。。。 フォルダではなく、ファイルなのですね! build内のindex.htmlでしょうか? ブラウザエラーではないですね。 Welcome Firebase Hosting Setup Complete You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary! こんな感じです! プロジェクトに飛ぶボタンはなく、go to docs しかないですね!
MOTOMUR

2017/11/07 10:40

What do you want to use as your public directory? (public) build ここはbuildとしたのですが。あってますか?
karamarimo

2017/11/07 10:47

> フォルダではなく、ファイルなのですね! あ、すみません誤解を招く表現でした。ビルドするといろいろファイルができますが、firebase にデプロイするときに指定するのはフォルダーです。 firebase deploy は実行しましたか?
MOTOMUR

2017/11/07 10:51

>firebase deploy は実行しましたか? 実行しました!
karamarimo

2017/11/07 11:16

そうすればデプロイできてるはずなんですが...。 実行した後 url とか表示されないですか? 公式ガイドには「ドメイン <YOUR-FIREBASE-APP>.firebaseapp.com にアプリがデプロイされます。」って書いてあります。
MOTOMUR

2017/11/07 11:19

そのページは表示されるのですが、デプロイしたはずの自分のプロジェクトは開かれないんですよね。
MOTOMUR

2017/11/07 11:27

またみてみると、build内のindex.htmlが上書きされていました。また直してみたのですが、アプリとしてbuildできていないのか何も表示できません。
karamarimo

2017/11/07 11:30

firebase init するのは一回だけでいいと思います。 firebase deploy したときだけ公開ページが更新されます。 上書きされた index.html を元に戻してから firebase deploy してみてください。
MOTOMUR

2017/11/07 11:35

deploy しても、さっきのこのページが表示されるばかりです。buildがうまくいってないのでしょうか? Welcome Firebase Hosting Setup Complete You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary! OPEN HOSTING DOCUMENTATION
MOTOMUR

2017/11/07 11:37

ちなみにURLはこれですplzdonation-56b64.firebaseapp.com
karamarimo

2017/11/07 11:44

こちらで開くとそのページは表示されず空白のページです。 ソースを見たところ、github にある public/index.html と同じなのですが、これをそのまま置いてますか? ビルドしたときにできる build/index.html はこれとはまた変わりますよ。
MOTOMUR

2017/11/07 11:54 編集

initしたときにバグったので、元に戻してしまっていました。 修正して、デプロイしたのですが、未だにページは何も表示できません。 firebase serveの場合は機能しているようです。
karamarimo

2017/11/07 12:00

いま見たらちゃんと表示されましたよ。 自動的に /login にリダイレクトされました。
MOTOMUR

2017/11/07 12:14 編集

確認ありがとうございます。ここからSignUpのデバックしていきたいのですが、サインアップ後のメールでちゃんとmakeMyPageに跳べるでしょうか?
karamarimo

2017/11/07 12:20

サインアップボタンの横に auth/invalid-emailThe email address is badly formatted. と表示されますね。
MOTOMUR

2017/11/07 12:22

それはfirebaseから受け取ったエラーですね。ちゃんとした形式じゃないと登録をはじくようになっているようです。
karamarimo

2017/11/07 12:51

あぁすみません勘違いしました。 入力欄が UserID になってるので直したほうがいいですよ。
MOTOMUR

2017/11/07 12:58

ああ間違っていますね。直します。
karamarimo

2017/11/07 12:59

確認メールが来て書いてあるリンクを開くと Your email has been verified You can now sign in with your new account と出て CONTINUE ボタンを押すと何も起きませんが、エラーがでてます。 Uncaught DOMException: Failed to execute 'assign' on 'Location': 'https://?link=http://plzdonation-56b64.firebaseapp.com/makeMyPage&apn=com.example.android&amv=12&ibi=com.example.ios&ifl=http://plzdonation-56b64.firebaseapp.com/makeMyPage' is not a valid URL. actionCodeSettings の iOS と android の項目を削除してみてください。
MOTOMUR

2017/11/07 13:04

項目削除しました。デプロイ完了しました。karamarimoさんのアドレスがfirebaseアカウントに登録されてしまったので、デバックに支障をきたすため、このURLのデバック作業が終わるまで継続的に削除しておきますね。
karamarimo

2017/11/07 13:39

ちゃんと飛びました。 ただ stepper が 2段階目のままですね。また、PREV NEXT ボタンは要らないのでは?
MOTOMUR

2017/11/07 13:44

ありがとうございます。そのようにdeployしました。 アカウントを消したので、もう一度だけサインアップのデバッグに付き合ってください。 今の状態で動きますでしょうか?
karamarimo

2017/11/07 13:51

確認メールが来ないですね...。
MOTOMUR

2017/11/07 13:57

firebaseの調子によるんですよね。。。 もう一度アカウント消させていただきます。
karamarimo

2017/11/07 14:00

すみませんやっぱり来てました。 リンクを踏むとちゃんと3段階目になってフォームが表示されました。
MOTOMUR

2017/11/07 14:11 編集

よかったです!ありがとうございます。これでGettiungStarted → signup → makeMyPageの流れが完成しました。これでユーザーの登録してもらえますね。 このページたちへのリンク方は、僕が暇なときにいじるとして、 新しく追加したい機能があります。 比較的時間はかからなそうなものから順に、 ①各ユーザーが投稿した時間を取得したいです。(その後にdatabaseのpostに時間の場所を確保して送ります。) ②starしているユーザーが新しい投稿をしたとき、Homeのそのstarユーザーのカードの左上か右上に、赤い丸を表示させて、その中に数字を表示したいです(その数字は何個新しい投稿があったか(数字に関してはなくてもいいです。)) つまりユーザーのデータの更新を参照したいです。 ③プロジェクトに言語ボタンをつけて、日本語と英語を切り替えたい。 ①は一人でなんとかできると思いますが、②、③はどうやっていいかアイディアがありません。どうしたらいいでしょう。
karamarimo

2017/11/07 14:35

②に関しては、ユーザーごとに、さらにそのスターしたユーザーごとに、最後に呼んだ投稿の日時をデータベースに記憶すればいいのではないでしょうか。 ref.startAt()とかchild_addedイベントを使えば、リアルタイムに表示を更新できると思います。 ③は、「react multiple languages」とかで調べればいろいろ出てくると思います。 react-intlというyahoo製の表示を国際化するパッケージがあるようですね。 https://github.com/yahoo/react-intl また、世に公開するおつもりならもう一つ、セキュリティの設定をする必要があります。 現在、おそらくどのユーザーも権限上はデータベースを読み書きし放題なので、クライアントがコードを書き換えれば簡単にデータベースを壊すことができます。 firebase のデータベースは場所ごとに権限を設定できるようです。 詳しくは公式ガイドをみてください。 database security https://firebase.google.com/docs/database/security/?hl=ja storage security https://firebase.google.com/docs/storage/security/?hl=ja
MOTOMUR

2017/11/07 14:50

ありがとうございます。②、③はあとでいじってみます。(不明点が出ればまた質問すると思いますが。。。) 先にルールを設定したいです。 読んだままの理解で少しいじってみました。 databaseのルール: { "rules": { "users": { "$uid": { ".write": "$uid === auth.uid" } }, "posts":{ ".validate": "newData.isString() && newData.val().length < 500" } } } ちなみに$uidとその中身はどういう意味を指しているのかわかりません。 それとルールの確認なんですけど、writeオンリーにしてしまったらrefもできなくなってしまいますよね? それとどのようにルールを設定したら抜け目ないでしょうか? 例があると助かるのですが。 storage securityの方はあまり理解ができませんでした。
MOTOMUR

2017/11/07 14:56

それと、検索機能って作用してますか? 自分のブラウザだと検索不能です。。。
MOTOMUR

2017/11/07 14:57

ルールのせいでしょうか。 { "rules": { "users": { "$uid": { ".write": "$uid === auth.uid", ".read":true, } }, "posts":{ ".validate": "newData.isString() && newData.val().length < 500" } } } こうもしてみましたが、検索はできません。。。
MOTOMUR

2017/11/07 15:02

元のルールに一度戻しました。
karamarimo

2017/11/07 15:24

> ちなみに$uidとその中身はどういう意味を指しているのかわかりません。 $uid は変数というかワイルドカードのようなものです。 users/ にあるすべてのキー $uid について、それが対象のユーザーの uid と一致すればそこへの書き込みが許される、ということですね。 "posts":{ ".validate": "newData.isString() && newData.val().length < 500" } とすると、post にデータを追加するとき、それが文字列である必要がある、となります。 確か各 post はオブジェクトでしたよね?代わりにhasChildren などを使えばいいのではないでしょうか。 apiについてはここに全部かいてあります。 https://firebase.google.com/docs/reference/security/database/ .read と .write のルールはカスケード(伝播)すると書いてあります。 つまり abc/ で .read = true とすれば、abc/def/geh への読み取りは許可されます。 しかも浅い所のルールのほうが優先されるので、注意が必要です。 先程の公式ガイドは左のメニューにいくつかページがあるので目を通してみてください。 > それと、検索機能って作用してますか? 「m」で検索したら3つ投稿が表示されました。
MOTOMUR

2017/11/07 15:28

>「m」で検索したら3つ投稿が表示されました。 先ほどのルール設定の状態では検索不能だったので、今は元どおりどこでもreadもwriteもtrueです。 ルールの記述法を勉強して書いてみますね。
karamarimo

2017/11/07 15:46

ちなみにですが、ブラウザで例えば直接 https://plzdonation-56b64.firebaseapp.com/signup に行くと 404 Page Not Foundと表示されます。 Single page application なので、どの url でも / にアクセスしてもらい、クライアントにルーティングを行わせる必要があると思います。 ここにあるとおりに firebase.json を編集するといいと思います。 https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites
MOTOMUR

2017/11/08 02:23 編集

ありがとうございます。ルールの設定でつまづいたのですが、例えば、postsでpostIdを挟んで、いろんな項目あると思うのですが、このpostIdの部分は$使ってどうやって書いていいんですかね?$postIdとはできなさそうなんですが。 こういう感じでいいんですかね?? { "rules": { ".read": true, "users": { ".validate": "newData.hasChildren()", "$uid": { ".write": "$uid === auth.uid" } }, "posts":{ ".validate": "newData.hasChildren()", "$post":{ ".write": "!data.exists() && newData.child('uid').val() == auth.uid" }, }, "comments":{ ".validate": "newData.hasChildren()", "$comment":{ ".write": "!data.exists() && newData.child('uid').val() == auth.uid" } }, } }
MOTOMUR

2017/11/08 02:29

スターされた側のstarPostやuserStarのルール分けが難しいです。 トランザクションのカウント側もユーザー欄も別の人がいじりますよね。。
MOTOMUR

2017/11/08 03:12

現在のルールを質問文に載せました。
karamarimo

2017/11/08 04:06

正直私もやったことがないので良いやり方が分からないですが、分かる範囲でお答えしますと、 > このpostIdの部分は$使ってどうやって書いていいんですかね?$postIdとはできなさそうなんですが。 どういう意味でしょうか?ワイルドカードなので名前は自由だと思いますが。 > ".validate": "newData.hasChildren()", としてますが、ちゃんと必要なキーがあるか確かめたほうがいいと思いますよ。 "uid":{ ".write": "$uid === auth.uid" }, ユーザーが勝手に uid を変えることができたらまずいですよね。書き込もうとしている値が $uid と等しいことを確かめたほうがいいと思います。 "$post":{ ".write": "!data.exists() && newData.child('uid').val() == auth.uid" }, これだとユーザーがあとから投稿を編集できないですね(編集機能を追加する予定がないならいいですが)。 既にデータがあればその uid が auth.uid と等しい必要がある、とすればいいのではないでしょうか。 "author":{ ".write": "!data.exists() && newData.child('uid').val() == auth.uid" }, newData は "author" キーに対するデータですから、newData.child('uid') は間違っているのでは? uid のチェックは $postの .write で行えばいいと思います。 > スターされた側のstarPostやuserStarのルール分けが難しいです。 トランザクションのカウント側もユーザー欄も別の人がいじりますよね。。 確かに面倒ですね。正直トランザクションに対してルールがどう働くのかよく分かってないです。 例えば、トランザクションでAしたあとBするとき、Bがチェックされる際、.val() にAの操作が反映されるのかどうか不明です。 また、データベースの構造が分からないとお答えしにくいので、スクショかコピペか貼っていただけませんか?
MOTOMUR

2017/11/08 06:28

写真追加しました。
MOTOMUR

2017/11/08 06:47

現在のルールです。まずいところあるでしょうか? { "rules": { ".read": true, "users": { ".validate": "newData.hasChildren(['explain','profile_image','starred_users', 'title','uid','user','userStar','user_posts'])", "$uid": { "user":{ ".write": "$uid === auth.uid", ".validate": "newData.isString() && newData.val().length <= 15", }, "uid":{ ".write": "!data.exists()", ".validate": "newData.isString() && newData.val().length <= 30", }, "explain":{ ".write": "$uid === auth.uid", ".validate": "newData.isString() && newData.val().length <= 300", }, "title":{ ".write": "$uid === auth.uid", ".validate": "newData.isString() && newData.val().length <= 20", }, "profile_image":{ ".write": "$uid === auth.uid", }, "user_posts":{ ".write": "$uid === auth.uid", }, "starred_user":{ ".write": "$uid === auth.uid", }, "userStar":{ ".write":true, }, } }, "posts":{ ".validate": "newData.hasChildren(['author','body','postComment','postId','postStar','title','uid'])", "$post":{ ".write": "!data.exists() || (newData.child('uid').val() == auth.uid && newData.child('uid').val() == data.child('uid').val())", }, }, "comments":{ ".validate": "newData.hasChildren(['author','body','commentId','uid'])", "$comment":{ ".write": "!data.exists() || (newData.child('uid').val() == auth.uid && newData.child('uid').val() == data.child('uid').val())", } }, } }
MOTOMUR

2017/11/08 06:50 編集

userStar postStar postCommentは本人以外も書き込めないとまずいので、ここのルール分けが難しいかもしれないですね。。
MOTOMUR

2017/11/08 06:58

新しいユーザー作ると、readできなくなって、mypageひらけないんですけど、ルールってアカウント作った時に影響しますか?
karamarimo

2017/11/08 07:03

> ルールってアカウント作った時に影響しますか? mypage でデータベースのどこが read できないのでしょうか? ルールは新旧アカウントにかかわらず適用されると思いますが。
MOTOMUR

2017/11/08 07:09

そうですよね。今のルールが間違ってるのだと思います。 この状態だと新規userの書き込みが不可能です。どのようにしたらいいでしょうか。
karamarimo

2017/11/08 07:26

users/$uid/uid で ".validate":"newData.isString() && newData.val().length <= 30" としていますが、auth.uid と等しい必要があるのでは? また、これは後回しでもいいと思いますが、 users/$uid/profile_image の .validate で url の形になっていることを確かめたほうがいいと思います。 regex を使えばできます。 https://firebase.google.com/docs/reference/security/database/regex 複数箇所を同時に update する時のためのルールは、調べてもいいやり方は見つかりませんでした。 少し古いですがここに書いてあるやり方だと、片方の場所から .parent() .child() を使ってもう1つの場所の newData を取得しに行くことができます。 https://groups.google.com/forum/#!topic/firebase-talk/rZzXgFjK2CE
karamarimo

2017/11/08 07:38

> この状態だと新規userの書き込みが不可能です。 ルールはコードでいうと ref.update とか ref.set とか ref.on のたびにチェックされますので、どのデータを同時に追加するのかといったことを慎重に考える必要があります。 私も先程適当にアドバイスしてしまいましたが、user にて ".validate":"newData.hasChildren(['explain','profile_image','starred_users','title','uid','user','userStar','user_posts'])" としているので、user以下のデータを追加・更新する際にこれらすべての値を指定する必要があります。 これらすべてを同時に追加していないために失敗するのだと思います。 makeMyPage のコードを github で見ようとしたのですが、profile_img のコードがないので古いままだと思います。 現在のコードを push していただけますか?
MOTOMUR

2017/11/08 07:42

pushしました。 haschildlenはやはり全て存在するデータじゃないと送れなくなりますよね。 どのように回避したらいいのか。。。
karamarimo

2017/11/08 07:52

とりあえず 'user_posts' は makeMyPage でセットしていないので、外しましょう。 どのようなルールにしたいのでしょうか? 「新規にページを作るときはすべてのデータを指定しなければならない」ならこうすればいいと思います。 ".validate":"data.exists() || newData.hasChildren(['explain','profile_image','starred_users','title','uid','user','userStar'])" ちなみに firebase のデータベースは空のオブジェクトを格納できないようです。makeMyPage で userStar:{ starCount:0, stars:{} } を追加していますが、この stars の部分はデータベース内に作られません。
MOTOMUR

2017/11/08 08:09

そうならstarred_userも外しておきますね。 最初にページを作った時のルールとすでにデータがある時のルールを作らなければいけないのですね。 starsやcommentsなどの設定が難しそうですね。
MOTOMUR

2017/11/08 08:11

現在のルールを質問文に載せますが、この状態だと、新しくページを作ることができません。
MOTOMUR

2017/11/08 08:29

>エラーは出てないですか? Uncaught (in promise) Error: PERMISSION_DENIED: Permission denied at Repo.js:510 at Object.t.exceptionGuard (util.js:556) at e.callOnCompleteCallback (Repo.js:501) at Repo.js:278 at PersistentConnection.js:411 at t.onDataMessage_ (PersistentConnection.js:444) at e.onDataMessage_ (Connection.js:262) at e.onPrimaryMessageReceived_ (Connection.js:256) at t.onMessage (Connection.js:157) at t.appendFrame_ (WebSocketConnection.js:197) こんなのはありますけど前からあったようななかったような。 >$を使って不正なキーに値を追加するのを禁止できるようです。 読んでみたのですが、例がチャットアプリなのか知りませんが、よくわからなかったです。
MOTOMUR

2017/11/08 08:30

ここはこうしてみました。 "starCount":{ ".write": "!data.exists()||newData.val() === data.val() + 1", },
MOTOMUR

2017/11/08 08:32

作成前だと反映されないかと思い。全てに!data.exists()||を追加しましたがこれはやりすぎでしょうか?
MOTOMUR

2017/11/08 08:45

".validate": "newData.hasChildren(['explain','profile_image','starred_users', 'title','uid','user','userStar','user_posts'])", こいつらってusersやpostsと、userやpostの下どっちにあるのが正しいですかね。。??
karamarimo

2017/11/08 09:58 編集

> PERMISSION_DENIED: Permission denied ルールに反したアクセスをした時に出るエラーらしいです。 https://stackoverflow.com/questions/37403747/firebase-permission-denied > 全てに!data.exists()||を追加しましたがこれはやりすぎでしょうか? .write に追加したんですか? !data.exists()||$uid === auth.uid としてしまうと、データが無い場合他のユーザーが書き込みできてしまいますよ。 > こいつらってusersやpostsと、userやpostの下どっちにあるのが正しいですかね。。?? explain などを child に持つのは各 user ですから、user に対して指定するのが正しいですね。 あと言い忘れていましたが、public/index.html にある firebase の script は不要ではないですか?
MOTOMUR

2017/11/08 10:06 編集

>public/index.html にある firebase の script は不要ではないですか? どうなのでしょう。。。消してやってみます。 追記:消しても動きましたね。不要でした。
MOTOMUR

2017/11/08 10:05

>.write に追加したんですか? そうやってました。間違っているので直しました。 現在のルールを質問文に更新します。
karamarimo

2017/11/08 10:16

> どうなのでしょう。。。 webpack をご存知ですか? react のビルドに使われているツールですが、それがあなたのコードとそれが依存しているライブラリを全てまとめて1つの js ファイルにビルドしてくれるからです。
karamarimo

2017/11/08 10:56 編集

> 読んでみたのですが、例がチャットアプリなのか知りませんが、よくわからなかったです。 { "abc": ..., "def": ..., "$others": { ".write": false } } とすれば、"abc","def"以外のキーは"$others"にマッチして、書き込みができなくなるということです。 users/$uid/uid/.write で "!data.exists()" としていますが、これにも $uid == auth.uid が必要なのでは? starCount と stars は Home で transaction で同時に更新するので、こんな感じでいいんじゃないでしょうか。理解できますでしょうか? "userStar":{ ".write": "newData.hasChildren(['starCount', 'stars'])" "starCount":{ ".validate": "data.exists() ? (newData.val() === (data.parent().child('stars').hasChildren(auth.uid) ? data.val() - 1 : data.val() + 1)) : ($uid === auth.uid && newData.val() === 0) ", }, "stars":{ "$starUid":{ ".validate": "auth.uid === $starUid && (data.exists() ? (newData.val() === null) : (newData.val() === true)) }, "$others": { ".write": false } }, "$others": { ".write": false } }, userStar にトランザクションを行うと、それ以下のすべての場所に書き込みが発生するのではないかと危惧していますが、その場合はこれだとうまくいきませんね。 ※修正しました。
MOTOMUR

2017/11/08 11:18

Error saving rules - Line 42: hasChildren() expects an array of child names. と言われます。 starCountの中身ですね。どう変えたらいいでしょう?
karamarimo

2017/11/08 11:26

あ、hasChildでしたね。すみません。 ついでに userStar/.write を .validate に変えといてください。
MOTOMUR

2017/11/08 11:30

そのように修正しました。
MOTOMUR

2017/11/08 11:30

質問文更新します。 この状態でもまだmakemypageはできないようです。
karamarimo

2017/11/08 11:44

userStar/.validate が最初作成するときを想定していませんでしたね。 "data.exists() ? newData.hasChildren(['starCount', 'stars']) : newData.hasChild('starCount')" にしてください。
MOTOMUR

2017/11/08 11:53

直しました。未だにマイページ作れませんね。。。どこがダメなんだかさっぱりです。。。
karamarimo

2017/11/08 12:01

simulator で試してみるとどこで引っかかってるか表示されるので、それで原因を調べてみてください。
MOTOMUR

2017/11/08 13:48

{ "268442d0-12d9-4e18-b44e-c9af72051553":{ "user":"mm", "uid":"268442d0-12d9-4e18-b44e-c9af72051553", "explain":"m", "title":"m", "profile_image":"http://www.aaa.com", "userStar":{ "starCount":0 } } } こんな感じでデータを送ったのですが、どこで引っかかったかわかりません。
MOTOMUR

2017/11/08 13:49

質問文にエラーの文を載せておきます。
karamarimo

2017/11/08 14:04

/makeMyPage に書き込もうとしていますね...。/users にしてください。
MOTOMUR

2017/11/08 14:11

シミュレーター的にurlと思いました笑 変えてみましたがダメですね。それに、どこがダメじゃなくて、write denyedだけですね表示されるの。 とりあえずシミュレーターの画像を質問文に追加しますね。
karamarimo

2017/11/08 15:00

不思議ですね...。私のほうで試しにやってみるとちゃんとどこで引っかかったか表示されるんですよね...。 https://imgur.com/RdiUxeI
MOTOMUR

2017/11/08 15:07 編集

ほんとですね。。。。とりあえず僕のシミュレーションしたデータの形式はあってますでしょうか? :誤解を招きそうな表現だったので訂正。
karamarimo

2017/11/08 15:14

たぶんあってます。 1つ原因として思いついたのは、例えば userCount への書き込みに対してルールがチェックされるとき、rootから辿っていくと1つも ".write"ルールが存在しませんが、その場合デフォルトで false になるのではないかと思います。 なので、どこを書き込む場合も少なくとも1つの ".write" が存在するように設定する必要があるのかもしれません。 試しに userStar/.write を true にしてみてください。
MOTOMUR

2017/11/08 15:25

"userStar":{ ".write":true, ".validate": "data.exists() ? newData.hasChildren(['starCount', 'stars']) : newData.hasChild('starCount')", "starCount":{ ".validate": "data.exists() ? (newData.val() === (data.parent().child('stars').hasChild(auth.uid) ? data.val() - 1 : data.val() + 1)): ($uid === auth.uid && newData.val() === 0) ", }, "stars":{ "$starUid":{ ".validate": "$starUid === auth.uid && (data.exists() ? (newData.val() === null) : (newData.val() === true))" }, }, "$others": { ".write": false } }, こういうことですか?この状態でも無理ですね。どこで否定されたかも言われません。。。
karamarimo

2017/11/08 15:38

うーん分からないですねぇ。 いったん今のルールを保存して simulator でなく webapp の方から書き込んでみてください。
MOTOMUR

2017/11/08 15:43

シミュレーターはいけませんでしたが、ページの作成には成功しました。(なんでやねん。) スター機能など、他のuidへのデータ操作に関係するところ以外はできるみたいです。
MOTOMUR

2017/11/09 00:29 編集

どうやらスター機能できていないのは昔作成したデバックアカウントだけでした。しかし、今の状態だとスターしたユーザーを消すことができません。 どのように直したらいいでしょう。
MOTOMUR

2017/11/09 00:38 編集

それと、homeの表示が、1ユーザーしかスターできない状態です。 一番最初にスターしたユーザーしか表示してません。 現在のコードに更新します。
karamarimo

2017/11/09 02:25

> 一番最初にスターしたユーザーしか表示してません。 それは表示だけの問題でしょうか? データベースで確認すると複数スターできてますか?
MOTOMUR

2017/11/09 02:30

> 一番最初にスターしたユーザーしか表示してません。 データベースのスターは増えます。複数可能です。
karamarimo

2017/11/09 02:38

Home ではスターしたユーザーの最新の投稿を表示するみたいな感じだったと思いますが、投稿は存在しますか?
MOTOMUR

2017/11/09 02:43

すいません。その問題でした。。。投稿のあるユーザーはスターできました。 しかしやはり、スターを外す機能は動かないようです。
karamarimo

2017/11/09 03:01

私が「危惧」しているといったことが起きているのかもしれません。 一人のユーザーを、複数人がスターすることはできますか?
MOTOMUR

2017/11/09 03:42

複数人がスターするの無理ですね。。。
karamarimo

2017/11/09 04:08

ならば、userStar にトランザクションを行うと それ以下のデータすべてを上書きするのかもしれません。 userStar/stars/$starUid/.validate に先頭に newData.val() === data.val() || を追加してみてください。 こうすることで、他人の $starUid についても同じ値なら上書きができるようになります。
MOTOMUR

2017/11/09 05:53

ありがとうございます!! できるようになりました!! ポストとかも試してみますね。
MOTOMUR

2017/11/09 05:56 編集

ポストのデリートがまずできません。 コメントすると、存在は出来上がるのですが、コメントの文面が表示されません。 他人へのコメントが表示されません。
karamarimo

2017/11/09 06:02

どこのコードのことでしょうか?
MOTOMUR

2017/11/09 06:11 編集

mypageにてDELETEボタンを押すと、消せたはずなのですが、消せなくなりました。 mypageにてコメントすると、コメントを送れますが、文面が表示できません。 homeにて、コメントしようとすると、コメントできません。
karamarimo

2017/11/09 07:08

> mypageにてDELETEボタンを押すと、消せたはずなのですが、消せなくなりました。 削除はなかなか面倒ですね...。投稿を削除するとそのコメントも削除する必要があるのでは? > mypageにてコメントすると、コメントを送れますが、文面が表示できません。 CommentRef が .once でコメントを取得しているからです。.on にしてみてください。off_postComments も使ってないのでついでに使えばいいのではないでしょうか。
MOTOMUR

2017/11/09 07:24

同期的に表示できるようになりましたが、なぜか表示されないなぁと思ったら、Linkyfyで書けないんですね。 <ListItem leftAvatar={<Avatar src='' />} primaryText={comment.author} secondaryText={<Linkify>comment.body</Linkify>} /> こんな感じにしたいのですが、このようにするにはどうやって書いたらいいんでしょう?
MOTOMUR

2017/11/09 07:26

デリートの関係はマジでどうしましょう。 デリート機能の見直しが必要ですよね。
karamarimo

2017/11/09 10:26

> こんな感じにしたいのですが、このようにするにはどうやって書いたらいいんでしょう? jsx でテンプレートに式を埋め込むので当然 {} が必要です。 <Linkify>{comment.body}</Linkify> > デリートの関係はマジでどうしましょう。 デリート機能の見直しが必要ですよね。 /posts からは消さずに /users/uid/user_posts/pid だけ消すのはどうですか?
MOTOMUR

2017/11/09 10:43

>jsx でテンプレートに式を埋め込むので当然 {} が必要です。 ありがとうございます。治りました。 >/posts からは消さずに /users/uid/user_posts/pid だけ消すのはどうですか? そうすると楽ですが、postsにはたくさん残ってしまいますね。。 でも無理に消して何もできないよりは良さそうな気もします。 アプリ的に全て消すように動かすのはさほど難しくはないのですが、ルール構築が大変になってしまうので、どちらがいいかは決めかねます。一長一短ですね・・・・
MOTOMUR

2017/11/09 10:46 編集

とりあえずデリート機能は置いておくとして、 他人にコメントできなくなったのを直したいです。 <どうやらこれはコードの問題ですね。まだ完成していないのを忘れていました。
MOTOMUR

2017/11/09 12:55

homeにてコメントするをクリックすると、 (in postRef) return firebase.database().ref().update(updates); が Error: Reference.update failed: First argument contains undefined in property 'comments.-KyVpMuIuuJKnoJa5WHJ.author' と言われるのですが、ちゃんと渡せている気がするのでなぜできないかわかりません。 (in Home) this.state.starred_posts.slice().map( function(starred_post){ return( <PostRef post={starred_post} myuid={this.state.userId} author={this.state.author} herfUserPage={this.herfUserPage.bind(this)} /> ) }.bind(this) fetchAuthorData4SubmitComment(){ const self =this firebase.database().ref('/users/'+this.state.userId).once("value").then(function(snapshot){ self.setState({ author:snapshot.val().user, }) }) } わかりづらいと思うので、現在のコードをgitにpushしておきます。
MOTOMUR

2017/11/09 13:09

デバックをしてみてたのですが、この現在の状態で、homeのfetchAuthorData4SubmitComment() にて、const authorまではデータがあるのですが、console.log()といましているところで、stateのauthorを表示すると、中身がnullです。どのように直したらいいでしょうか。
MOTOMUR

2017/11/09 13:15 編集

ついでにrenderの中にstateの中身をぶち込むと、数回表示されて、後半の方はちゃんとデータがありました。しかし、やはり中身は空になってしまうようです。 <<<追記です>>> Error: Reference.update failed: First argument contains undefined in property 'comments.-KyVpMuIuuJKnoJa5WHJ.author' これについては修正できました。consoleの表記場所で変な動きになっていたのですね。 コメントできない原因はデータベースのセキュアがダメなようですね。
karamarimo

2017/11/09 13:36

> console.log()といましているところで、stateのauthorを表示すると、中身がnullです。 self.setState({author}, console.log(self.state.author)) それは間違ってますね...。setStateの第2引数には関数を渡します。 self.setState({author}, () => console.log(self.state.author)) > ついでにrenderの中にstateの中身をぶち込むと、数回表示されて、後半の方はちゃんとデータがありました。しかし、やはり中身は空になってしまうようです。 state とはどの home のですか? 数回表示されるというのは何をしたらそうなったのでしょうか? 空になってしまうとはどの部分がですか? 以下気になった点: referLatestPost で最新の投稿の id を取得してからわざわざ fetchLatestPosts でまた各投稿を取得していますが、referLatestPost で得た snapshot にその情報が入っているのではないでしょうか。 また、ビルドしてできるファイルはgithubに入れないほうがいいと思います。
karamarimo

2017/11/09 13:42

投稿はできるのでしょうか? また質問文にあるルールは最新ですか?
MOTOMUR

2017/11/09 13:48 編集

現在いじったものをここと質問文にあげたいと思います。 "postComment":{ ".write":true, ".validate": "data.exists() ? newData.hasChildren(['commentCount', 'comments']) : newData.hasChild('commentCount')", "commentCount":{ ".validate": "(data.exists()) ? (newData.val() === data.val() + 1):(newData.val() === 0)" }, "comments":{ "$commentId":{ ".validate": "newData.val() ===root.child('comments').child('$comment').key||(data.exists()?(newData.val() === null) : (newData.val() === true))" } }, "$others": { ".write": false } }, こんな感じで書いてみたのですがどうでしょうか? 投稿の機能自体は最後まで機能するのですが、データベースに反映されません。 追記:.keyみたいな感じで判定させたいのですが、セキュアにはこの機能はないみたいですね・・・ なのでこのルールではpublishできません。
karamarimo

2017/11/09 14:04

PostRef のコードを見ると、 handleCommentSubmit = (val) =>{ this.writeNewComment(val) this.addCommentCount() } コメントの追加とコメント数のインクリメントを別々にやってますよね。 しかしそのルールは同時にやることを想定しています。 コードの方を変えてはどうでしょうか? また、userStar の方もそうですが、"postComment"で .write=true とすると、それより下も強制的に .write=true になってしまうんですよね...。なので $others/.write = false は意味がないんです。言い忘れていました。 とりあえず面倒なので $others は消してもいいかもしれません。ユーザーは適当なキーに値を入れ放題になりますが。 また、昨日?言ったように .write が先祖に存在しない場所があると書き込めないので、埋めてください。
MOTOMUR

2017/11/09 14:34

埋めてみました。コメントは増やせるのですが、コメントカウントが増やせません。 現在のルールに質問文を更新します。
MOTOMUR

2017/11/09 14:49 編集

こうしたらいいかなと思って深いところにwrite書きましたが、一個でも空いてるとfalseになるんですね。。。 >なので質問文をもう一度更新します それと、やはりスターユーザーのstarをやめられないですね。どこがまずいんでしょうか。
MOTOMUR

2017/11/09 15:03 編集

それと、homeのLatestPostをいじってたのですが、何も表示されません。なぜでしょうか。。 gitに最新のコードがpushされています。
karamarimo

2017/11/09 15:09

> コメントは増やせるのですが、コメントカウントが増やせません。 削除する際のルールは newData.val() === null ではなく、 !newData.exists() としなければいけないのかもしれません。 $starredUid $starUid $commentId のところをそのように修正してみてください。
MOTOMUR

2017/11/09 15:14

>!newData.exists() としなければいけないのかもしれません。 それでもダメですね。。
karamarimo

2017/11/09 15:15

> homeのLatestPostをいじってたのですが、何も表示されません。なぜでしょうか。。 とりあえず先程言った通り referLatestPost と fetchLatestPosts はやってることが二度手間なので、修正してみてください。
MOTOMUR

2017/11/09 15:23 編集

ほんとですね。二度手間でした。気づきませんでした。。。 >表示可能になりました。 やらなければならないコーディング多くて一人で全て把握するの大変で混乱しますね。。。。 追記: とりあえず自分で解決不能なのは、コメントカウントを増やすことと、スターを2度目押した時消えないことですね。
karamarimo

2017/11/09 15:31

> それでもダメですね。。 これこそ simulator でやってみてもらえませんか? starCount と stars を同時に減らしてみてください。 > 全て把握するの大変で混乱しますね コンポーネントの構成が複雑化してきているような気がしますが、container component と presentaitional component をしっかり分離して作ると見通しがよくなると思います。 http://minotaur.badwitch.io/container-and-presentational-component/ もちろん後回しでもいいですが。
MOTOMUR

2017/11/09 15:48

>starCount と stars を同時に減らしてみてください。 ごめんなさい。この場合、シミュレーターの値はどのように設定したらいいでしょうか。
karamarimo

2017/11/09 16:02

userStar に対して { starCount: [1減った数], stars: [1つ減らしたもの] } を書き込んでください。 当然、削除する id と 書き込み主の id は一致する必要があります。
MOTOMUR

2017/11/10 03:06

{ "F8JAjm4x78Z1Yu1huZW5RXOMXsj1":{ "userStar":{ "starCount":"0", "stars":{ "mtJPZ3e7eLYWbh1rsBKrqKI3rTb2":"", },} }} これだと Invalid JSON らしいのですが、ちゃんと書いてるつもりなのですが。。。
karamarimo

2017/11/10 05:15

JSONでは trailing comma は禁止されています。 次の項目があるときだけコンマを入れることができます。 あと、stars 内は "uid": true という形ではなかったですか?
MOTOMUR

2017/11/11 16:10

返事遅れて申し訳ありません。パソコンから離れた休日を過ごしていました。 { "F8JAjm4x78Z1Yu1huZW5RXOMXsj1":{ "userStar":{ "starCount":"0", "stars":{ "mtJPZ3e7eLYWbh1rsBKrqKI3rTb2":"null" } } } } このように、シミュレータを動かしてみたのですが、denyedでした。しかし詳細は現れなかったので、どこが引っかかったのか全くわかりません。。。
karamarimo

2017/11/11 18:06

すみません。どうやら私の理解が間違っていたようです。 例えば Home.js の addStar で users/id/userStar に対して書き込みを行っています。 このコードに対しては、「userStar もしくはその先祖」に .write ルールを用意する必要があります。 いくら子孫のノードに .write を書いてもダメです。 firebase はトップダウンにルールをチェックしますので、書き込みを行う ref の場所ですでに .write が通っている必要があります。 また、見落としてましたが、.validate は削除の際にはチェックされないようです。 https://firebase.google.com/docs/database/security/securing-data?authuser=0#validating_data
MOTOMUR

2017/11/12 03:48

なるほど。 具体的には$uidですでにwriteのルールを書かないといけないわけですね。 つまりこの部分、 "$uid": { ".write":"$uid === auth.uid||newData.child('userStar').hasChild('starCount')", をいじらないといけないわけですね。この書き方だとuserStarはwriteがtrueにはならないようですね。この右側の部分のルールの設定はどうしたらいいでしょうか。
MOTOMUR

2017/11/12 04:10

それと、投稿に投稿時間のデータを渡したりする件ですが、 firebase.database.ServerValue.TIMESTAMP これを用いて取得しようとして、中身の形がわからなかったので、consoleで確認したら、{.sv:timestamp}となっていて、どこが時間のデータがかわかりませんでした。。。 どのようにこのデータを扱ったらいいでしょうか? また、時間の扱いはどのようにしたらいいでしょうか?moment.jsを使おうかなと思っていますが、何時間前とか何分前のような表記に向いてますでしょうか?
karamarimo

2017/11/12 05:53

あーまた私の誤解が発覚しました。 あるノードで .write が false になっても、子孫ノードで .write が true になるとそこには書き込みが許されます。 カスケードするのは .read/.write = true の場合だけのようです。 いったんどこかで .write = true になると、それより下にある .write は無視されます。 例 https://imgur.com/a/zJxpr > "$uid": { ".write":"$uid === auth.uid||newData.child('userStar').hasChild('starCount')", いや、それだと書き込む値が userStar/starCount さえ含んでいれば他人でもたとえば title を書き換えることができてしまいます。$uid ではとりあえず ".write": "$uid === auth.uid" としておけばいいと思います。
karamarimo

2017/11/12 06:26

スターが減らせないのは別のところが原因です。userStar/.validate にて "data.exists() ? newData.hasChildren(['starCount', 'stars']) : newData.hasChild('starCount')" スターを減らした結果スター数が0になると、stars は空になりますよね。そうすると stars は存在しないことになり newData.hasChildren(['starCount', 'stars']) が false になります。 ここは単純に "newData.hasChild('starCount')" としておけばいいと思います。 また、先程いったように削除に対しては .validate は無視されます。なので $starUid/.validate にて newData.val() === data.val() || $starUid === auth.uid && (data.exists() ? (newData.val() === null) : (newData.val() === true)) この newData.val() === data.val() と newData.val() === null は意味がありません。 newData.val() === true だけにしておきます。 .validate では他のユーザーのスターを勝手に削除するのを防ぐことができません。ということは .write で防ぐ必要があります。しかし現状では uesrStar に書き込みを行っているため、そこで .write の判定を行わなければなりません。でもそれでは他のユーザーのスターを削除していないかをすべてチェックするのは無理です(たぶん)。 代わりに、コードの方を変えましょう。 現状ではトランザクションで starCount が必ず1増えるか減ることを保証していますが、これはルールの方でチェックするだけにしましょう。 そしてトランザクションでなく update で stars と starCount を同時に更新するようにすれば、 uesrStar に .write ルールを書く必要がなくなります。 参考 https://stackoverflow.com/questions/37954217/is-the-way-the-firebase-database-quickstart-handles-counts-secure/37956590#37956590 あと、至る所に "$others": { ".validate": false } と書いておけば不正なキーへの書き込みが防げます(othersという名前には意味はありません)。
MOTOMUR

2017/11/12 14:35

ありがとうございます。 https://stackoverflow.com/questions/37954217/is-the-way-the-firebase-database-quickstart-handles-counts-secure/37956590#37956590 これを参考にして書き換えていたのですが、 addStar(uid) { const myUserId = this.state.userId const starUserData = uid var add = true const self = this firebase.database().ref('users/' + starUserData + '/userStar').once("value",function(count) { if(count.stars&&count.stars[myUserId]){ var updates = {} updates['stars/' + myUserId]= null updates['starCount']= count.starCount-- return firebase.database().ref().update(updates) }else{ var updates = {} updates['stars/' + myUserId]= true updates['starCount']= count.starCount++ return firebase.database().ref().update(updates) } }).then(function (result) { if (result.snapshot) { const updatedUser = result.snapshot.val() for(var i = 0 ; i<self.state.search_users.length ; i++){ if(self.state.search_users[i].uid===uid){ const newSearchUsers = update(self.state.search_users, { [i]: {userStar: {$set: updatedUser} } }) self.setState({search_users: newSearchUsers}) } } } }) } こうすると、追加の時も削除の時も Error: Reference.update failed: First argument contains NaN in property 'starCount' と出るのですが、どこが間違えているでしょう?
MOTOMUR

2017/11/12 14:36

現在のルールに更新しておきます。
karamarimo

2017/11/12 15:22

count.starCount が undefined になってるからです。 transaction とは異なり、once のイベントハンドラーに渡されるのは snapshot です。 snapshot.val() で中のデータを取り出してください。 ...once("value",function(snapshot) { const starData = snapshot.val() // userStar におけるデータ ... } また、then のつなぎ方がおかしいですね。 return firebase.database().ref().update(updates) としていますが、これはイベントハンドラー内で return しているので外側の then につながりません。 今ある then は once のイベントハンドラー的なものになっているので、結果イベントハンドラーが2つになってます。 また、ref.update が返す promise は何も含んでおらず、成功したかどうかしか分からないです。 https://firebase.google.com/docs/reference/js/firebase.database.Reference#update なのでこんな感じにしてください。 https://pastebin.com/P2nhWyLN
MOTOMUR

2017/11/12 16:01

addStar(uid) { const myUserId = this.state.userId const starUserData = uid var add = true const self = this firebase.database().ref('users/' + starUserData + '/userStar').once("value").then(function(snapshot) { const starData = snapshot.val() if(starData.stars&&starData.stars[myUserId]){ var updates = {} updates['stars/' + myUserId]= null updates['starCount']= starData.starCount-- return firebase.database().ref().update(updates) }else{ var updates = {} updates['stars/' + myUserId]= true updates['starCount']= starData.starCount++ return firebase.database().ref().update(updates) } }).then(function (result) { if (result.snapshot) { const updatedUser = result.snapshot.val() for(var i = 0 ; i<self.state.search_users.length ; i++){ if(self.state.search_users[i].uid===uid){ const newSearchUsers = update(self.state.search_users, { [i]: {userStar: {$set: updatedUser} } }) self.setState({search_users: newSearchUsers}) } } } },function(err){ console.log("in addStarFunction error") }) } そのようにしてみましたが、console.logのエラー側に行っちゃうようですね。。 onceってonce("value",function() の形じゃなくてもできるんでしょうか? それとも、.then(function (result) {の中身をいじらなかったからうまくいかないのでしょうか?
karamarimo

2017/11/12 16:44

> そのようにしてみましたが、console.logのエラー側に行っちゃうようですね。。 エラーを出力してみてください。たぶん permission error だと思いますが。 > onceってonce("value",function() の形じゃなくてもできるんでしょうか? 今までも出てきたと思いますが、 once("value", function (snapshot) { ... }) と once("value").then(function (snapshot) { ... }) は同じです。別にこれは javascript 全般の話ではなく、たまたま firebase の api が両方の形に対応してるだけです。 > それとも、.then(function (result) {の中身をいじらなかったからうまくいかないのでしょうか? 先程言ったように、update が返す promise には何もデータは含まれません。 https://firebase.google.com/docs/reference/js/firebase.database.Reference#update returns firebase.Promise containing void と書いてありますよね。 なので result には何も入りません。
MOTOMUR

2017/11/13 02:09

addStar(uid) { const myUserId = this.state.userId const starUserData = uid var add = true const self = this firebase.database().ref('users/' + starUserData + '/userStar').once("value").then(function(snapshot) { const starData = snapshot.val() if(starData.stars&&starData.stars[myUserId]){ var updates = {} updates['stars/' + myUserId]= null updates['starCount']= starData.starCount-- return firebase.database().ref().update(updates) }else{ var updates = {} updates['stars/' + myUserId]= true updates['starCount']= starData.starCount++ return firebase.database().ref().update(updates) } }).then(function () { const self = this firebase.database().ref('/users/').orderByChild('user').startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value").then(function(snapshot) { const search_users =[] snapshot.forEach(function(childSnapshot) { search_users.push(childSnapshot.val()) }) self.setState({ search_users:search_users, }); }) },function(err){ console.log(err) }) } >たぶん permission error だと思いますが。 そのようですね。色々改良してみましたが、現在のこのコードでも未だにそれです。
MOTOMUR

2017/11/13 02:39

ごめんなさい。それとfirebaseのtimestampの扱い方を勉強していましたが、いまいちどのような形でtimestampを取得できるのかわからず、困っています。 firebase.database.ServerValue.TIMESTAMPを任意の値に変換するにはどうしたらいいでしょうか?
karamarimo

2017/11/13 03:45

> それとfirebaseのtimestampの扱い方を勉強していましたが、いまいちどのような形でtimestampを取得できるのかわからず、困っています。 少し回りくどいですが、書き込んでからvalue イベントで取得できます。 https://stackoverflow.com/questions/37864974/ https://stackoverflow.com/questions/34718668/ > firebase.database.ServerValue.TIMESTAMPを任意の値に変換するにはどうしたらいいでしょうか? 変換するとは例えば文字列でしょうか?何のために変換するのでしょうか?
karamarimo

2017/11/13 04:35

users/$uid/.validate が "data.exists() || newData.hasChildren(['explain','profile_image','title','uid','user','userStar'])", となってますが今見るとおかしいですね。 これらのキーは常に必須なので "newData.hasChildren(['explain','profile_image','title','uid','user','userStar'])" としておいてください。 あと、.write = true はカスケードするので、users/$uid/.write で $uid === auth.uid としてしまうと、それが true になるとき(つまりユーザー自身であるとき)に stars/starUid/.write: "auth.uid == $starUid" の意味がなくなってしまいます。 userStar/.write: true も同様です。 面倒ですが、この2つを削除して 'explain','profile_image','title','uid','user','userStar',"user_posts","starred_user" に .write: $uid === auth.uid を設定してください。 あと userStar ですが... 先述のとおり .validate は削除の際にチェックされませんので、stars から削除する際のチェックを .write に書く必要があります。 こうしてみてください。 https://pastebin.com/Jk4RwrLW また、permission error が出るのはどのような状況ですか?
MOTOMUR

2017/11/13 07:52

>permission error が出るのはどのような状況ですか? スターする時も削除する時もパーミッションエラーですね。 ルールはそのように修正しました。質問文のコードを修正しておきます。 >変換するとは例えば文字列でしょうか?何のために変換するのでしょうか? firebaseに書き込んで取得することをしておらずデータの形が{.sv : timestamp}となっていたため、何か特殊な変換が必要かと思いました。 今では普通に数列を得たので、react-timestampを使ってみやすくしようと思います。
MOTOMUR

2017/11/13 08:07

先ほどgitにコードをアップしました。 timestampの件なのですが、postRef4MyPageにて、 <CardText> <p><Timestamp time={this.props.post.timestamp} format='full' includeDay/></p> としているのですが、*1000ができていないので値がやばいです。 どこで*1000したらいいでしょうか? それと今は表記が楽なように暫定的にここに置いていますが、 <CardTitle title={this.props.post.title} subtitle={" star:(" + this.props.post.postStar.starCount + ")"} /> ここのサブタイトルの方に入れたいのですが、文法がわかりません。 this.props.post.timestampをどうやって*1000してこのサブタイトルに入れればいいでしょうか(subtitleでできなければ本文でもいいですが。)? スターの前に入れたいです。
karamarimo

2017/11/13 08:55

*1000 とは...? https://firebase.google.com/docs/reference/js/firebase.database.ServerValue timestamp は miliseconds ですが、react-timestamp は桁数的に seconds ということでしょうか。 ならば 1000 で割ればいいのでは? time={this.props.post.timestamp / 1000} また、今のコードは tmp/timestamp に書き込んで時刻を取得しそれをさらに投稿データに含めていますが、投稿する時に直接 firebase.database.ServerValue.TIMESTAMP を指定すればいいのでは? > this.props.post.timestampをどうやって*1000してこのサブタイトルに入れればいいでしょうか こうですかね。別に span でなくてもいいと思いますが。 subtitle={ <span> <Timestamp time={this.props.post.timestamp} format='full' includeDay/> star:({this.props.post.postStar.starCount}) </span> }
MOTOMUR

2017/11/13 10:05

>また、今のコードは tmp/timestamp に書き込んで時刻を取得しそれをさらに投稿データに含めていますが、投稿する時に直接 firebase.database.ServerValue.TIMESTAMP を指定すればいいのでは? 直接だと、{.sv : timestamp}となってしまってできなかったので、こうしてみました。 ありがとうございます!timestampの件うまくいくようになりました! >permission error が出るのはどのような状況ですか? この件ですが、解決方法ありますでしょうか。
karamarimo

2017/11/13 11:20

スターするコードは Home.js の addStar ですよね? updates['starCount']= starData.starCount++ これはまずいですね。 例えば x = 3 なら x++ は 3 を返します。 素直に starData.starCount + 1 とすればいいのではないでしょうか。 -- の方も同様です。
MOTOMUR

2017/11/13 11:25

ありがとうございます。インクリメントはこの場合できないのですね。 とりあえずそれはなおしましたが、permission errorになりますね。 他にはどこに改善策あるでしょうか。
karamarimo

2017/11/13 11:45

> インクリメントはこの場合できないのですね。 インクリメントできないのではなく、返す値が増える前の値になるだけです。 var x = 1 var y = x++ とすると、x = 2, y = 1 となります。
karamarimo

2017/11/13 11:50 編集

updates['stars/' + myUserId]= null updates['starCount']= starData.starCount-- return firebase.database().ref().update(updates) アップデートする場所が間違ってます。 ref('users/' + starUserData + '/userStar') ですね。
MOTOMUR

2017/11/13 14:38 編集

ありがとうございます。permission errorは改善されました。 スターされた側の動作しか書いていなかったので、 addStar(uid) { const myUserId = this.state.userId const starUserData = uid const self = this firebase.database().ref('users/' + starUserData + '/userStar').once("value").then(function(snapshot) { const starData = snapshot.val() if(starData.stars&&starData.stars[myUserId]){ var updates = {} updates['users/' + starUserData + '/userStar/stars/' + myUserId]= null updates['users/' + starUserData + '/userStar/starCount']= starData.starCount-1 updates['users/' + myUserId + '/starred_user/' + starUserData]= null return firebase.database().ref().update(updates) }else{ var updates = {} updates['users/' + starUserData + '/userStar/stars/' + myUserId]= true updates['users/' + starUserData + '/userStar/starCount']= starData.starCount+1 updates['users/' + myUserId + '/starred_user/' + starUserData]= true return firebase.database().ref().update(updates) } }).(以下略) こうしてみたのですが、starred_user側への書き込みができないようです。ルールの問題でしょうか。 コードかルールどちらかめんどくさくないように変更したいのですが、どのように変更するのが最善でしょうか?
MOTOMUR

2017/11/13 15:10

ごめんなさい。自分でデバックしていたら、ルールと更新でstarred_userにしていたことが発覚しました。正しくはstarred_usersです。修正できました。 次の問題はコメントのカウントが増えないことなので、同時更新にしてルールを変えたいと思うのですが、コメントのルールに困っています。
karamarimo

2017/11/13 15:12 編集

コードを追加する前はスターできたんでしょうか? エラーは permission error ですか? EDIT: すみません入れ違いでした。
MOTOMUR

2017/11/13 15:17 編集

そして入れ違いなのですが、コメントカウントはインクリメントできないのにしていました。治りました。 コメントのデリート機能を何とかなおしたいです。 参照場所間違えました。正しくはpostRef4MyPageです。 postRef4MyPage.jsにコードがあると思うのですが、deleteを押しても削除不能です。どのように変更したら治るでしょうか。
karamarimo

2017/11/13 15:20

deleteのコードが見当たらないです...。どこでしょうか。
MOTOMUR

2017/11/13 15:20

補足ですが、gitとは違うパターンで deleteBtnClick = () =>{ var deletePostKey = this.props.post.postId firebase.database().ref('posts/'+deletePostKey).remove() } としてもダメでした。gitの方が消せる可能性のたかそうな動作でした。
MOTOMUR

2017/11/13 15:21

参照場所間違えておりました. postRef4MyPageの方です。。。
karamarimo

2017/11/13 15:37 編集

posts の方も users と同じように書き換える必要がありますね...。 とりあえず posts/$post/.write を !data.exists() || (newData.child('uid').val() === auth.uid && newData.child('uid').val() === data.child('uid').val()) || data.child('uid').val() === auth.uid && !newData.exists() とすれば .remove で削除できると思います。
MOTOMUR

2017/11/14 00:51

ありがとうございます。これで削除できますね。
MOTOMUR

2017/11/14 02:49 編集

material-uiのiconsについて質問なのですが、 現在このプロジェクトのスターの表記が文字で格好悪いので、アイコンに数字を表示させたいのですが、 そのようなことは可能なのでしょうか?自分が調べた限りではできなさそうなのですが。 できたらそのようにしたいです! それと、ポストの画像については任意にしたいのですが、ルールとコードでの対応が難しそうなのですが、実現可能でしょうか?
karamarimo

2017/11/14 03:19

> アイコンに数字を表示させたいのですが、そのようなことは可能なのでしょうか? バッジを使えばいいのでは? http://www.material-ui.com/#/components/badge > それと、ポストの画像については任意にしたいのですが、ルールとコードでの対応が難しそうなのですが、実現可能でしょうか? ルールは hasChildren から profile_image を消せばいいと思いますが、そもそも今のルールがおかしいですね。 "$post":{ ".write": "newData.child('uid').val() === auth.uid && newData.child('uid').val() === data.child('uid').val()", ".validate": "newData.hasChildren(['author','body','postComment','postId','postStar','title','uid','timestamp'])", あれ...?PostRef.js には profile_image と post_image 両方ありますが、どうなっているのでしょうか。
MOTOMUR

2017/11/14 03:50

>バッジを使えばいいのでは? こんなのがあったのですね。使います。 ポストイメージの方は、投稿した画像ですが、プロフィールイメージの方はポストのアバター用の写真です。
MOTOMUR

2017/11/14 03:55

ルールについて確認したいのですが、newData.haschildで、かっこの中身に書かれてるのは、それらがないと投稿できないという意味でしょうか? ポストイメージは任意ですが、プロフィールイメージは必須なので、プロフィールイメージをかっこの中に増やしました。
karamarimo

2017/11/14 04:15

postの profile_image とは、ユーザーの profile_image とは違うのでしょうか? > newData.haschildで、かっこの中身に書かれてるのは、それらがないと投稿できないという意味でしょうか? docs を見ていただければ分かると思いますが、hasChild は キーあるいは path が存在するかどうかです。 foo にて data.hasChild('bar') とすれば、foo/bar にデータが存在するかどうかです。 https://firebase.google.com/docs/reference/security/database/ 空オブジェクトは存在しない扱いになるので注意が必要です。 また、newData は「もし書き込みが許可されたらこうなる」という情報です。なので newData.hasChild('bar') は書き込んだ結果 bar が存在するということですから、bar に書き込む必要はありません。
MOTOMUR

2017/11/14 04:58

>postの profile_image とは、ユーザーの profile_image とは違うのでしょうか? 同じですが、postRefではポストを参照するのでプロフィールイメージを取ってくるのに手間がかかるため、ポストに組み込んでしまいました。 分解する場合はpostRefにてそれぞれfirebaseを呼び出してとなってしまうのですが。 データベースの管理を考えるとこの分解の方がいいでしょうか? > newData.haschildで、かっこの中身に書かれてるのは、それらがないと投稿できないという意味でしょうか? ごめんなさい。質問のコードが違いましたhasChildrenの方を聞こうと思っていました。 しかし、childの説明で大方理解できました。 つまり、hasChildren()内に存在して欲しいものを書いた場合、なければ書き込めず、あれば書き込めるということでね。 ここに含まれてないものも更新可能ということでしょうか?
karamarimo

2017/11/14 05:26

> 分解する場合はpostRefにてそれぞれfirebaseを呼び出してとなってしまうのですが。 データベースの管理を考えるとこの分解の方がいいでしょうか? そのほうがいいと思います。 > ここに含まれてないものも更新可能ということでしょうか? hasChildren は「このキーが存在しなければならない」というだけで他のキーについての制限はしません。
MOTOMUR

2017/11/14 06:07

まず、gitにあるpostRefのwriteNewCommentと質問文の更新したルールを見て欲しいのですが。 コメント機能をスター機能同様にカウントとコメントのIDを同時に動かそうとしたのですが、うまくいきません。どこに不備があるでしょうか。 postRefの件は後ほど修正いたします。
karamarimo

2017/11/14 06:31

$comment/.write が 新規に追加するときに false になりますね。 data.exists() ? (newData.child('uid').val() === auth.uid && newData.child('uid').val() === data.child('uid').val()) : newData.child('uid').val() === auth.uid $post/.write の方も同様に修正してください。 ちゃんとやろうとすると users のように .write を各フィールドに書かなければいけないですが...。
MOTOMUR

2017/11/14 06:49

この状態でもコメント書き込み不能ですね。 どこがおかしいでしょう。
MOTOMUR

2017/11/14 06:54

それと、postRef4LatestUserを追加し、そこにスター機能を追加したのですが、スターしたり解除したら、それが即時に反映されるように作りたいのですが、Home内でのそれはどうやるかわかるのですが、ファイルを挟むとどうやっていいのかわからないので、教えてください。
karamarimo

2017/11/14 07:31

同じようなコンポーネントをいくつもつくると管理が面倒だったりミスが発生しやすくなるのでおすすめしません。 props でスター機能をon/offできるようにするとか、共通部分だけ取り出してコンポーネントにするなどしたほうがいいです。
karamarimo

2017/11/14 09:58

> postRef4LatestUserを追加し、そこにスター機能を追加したのですが、スターしたり解除したら、それが即時に反映されるように作りたいのですが state.star を更新するだけでできますよ。 同じスター追加のコードが複数箇所にあるのはまずいです。 この際スター機能やコメント機能含め、データベースへの書き込む処理はすべて1つの独立したjsファイルに入れてはどうでしょうか。
MOTOMUR

2017/11/14 10:56

そうですね。分解しようと思います。 不明点あった場合は質問させていただきます。 コメント機能ですが。こちらもまとめていこうと思うのですが、すでに機能しません。コードの問題ですかね?
karamarimo

2017/11/14 10:58

とりあえずコメントを送信する時に何かの値がnullか undefined になっていないか確かめてください。
MOTOMUR

2017/11/14 11:30

その前にスターボタンを分けていたのですが、自分なりの解釈では現在のgitのStarBtnのようになったのですが、リアルタイムでボタンを変更できませんね。 starを更新するとはこういうことではなかったのですね。。。 どのようにしたら、使われているpostRef4LatestUserとhomeにて違わず機能させられるでしょうか? もともと、homeにスターボタンがあった時は、 スター後に .then(function () { firebase.database().ref('/users/').orderByChild('user').startAt(self.state.search).endAt(self.state.search+"\uf8ff").once("value").then(function(snapshot) { const search_users =[] snapshot.forEach(function(childSnapshot) { search_users.push(childSnapshot.val()) }) self.setState({ search_users:search_users, }); }) },function(err){ console.log(err) }) このように更新していました。
MOTOMUR

2017/11/14 12:08 編集

myPageでデバックしていたところ、postRef4MyPage上での動作がうまくいったので、commentformにコメント機能を写したのですが、急に Error: Reference.update failed: First argument contains undefined in property 'comments.-KyuPqlGHcsCbHVjxFy0.author' と言われました。 現在のコードはgitに上がっています。propsを使い引数はうまく渡せたと思うのですが。 ちなみにauthorの中身はundefinedとなってしまいます。
MOTOMUR

2017/11/14 15:15

ファイルを多くしてしまって混乱してますね。。。ありがとうございます。 コメントフォームのボタンがクリックされたらpostRefのstateを書き換えたいのですが、 すでにコメントフォームのonClickにpropsじゃない関数を定義してしまっているため、自分の知識ではできません。どのようにコメントフォームのonclickないの関数をポストレフで呼び出せばいいのでしょう。。
MOTOMUR

2017/11/14 15:33

さらにグダグダになってきたのですが、mypageにpostingもできなくなってしまいました。。
karamarimo

2017/11/14 16:34

> どのようにコメントフォームのonclickないの関数をポストレフで呼び出せばいいのでしょう。。 そうではなく、CommentForm が送信されたら PostRef の関数を呼び出す、ではないですか? CommentForm に prop として関数を渡し、CommentForm は送信時にその関数を実行すればいいと思います。たとえば PostRef にて <CommentForm onSubmit={this.onCommentSubmit(comment)}> としてCommentForm で handleCommentSubmit = () => { const commentData = { ... } this.props.onSubmit(commentData) } 現在 CommentForm でコメント送信処理をやってますが、PostRef で全部やったほうがいいのではないでしょうか。 PostRef を container component 、CommentForm を presentational component 的にする感じで。
MOTOMUR

2017/11/16 06:49

そのようにしてみたのですが、ルールが多分うまく機能しておらず、コメントが反映されません。 現在のルールを更新しておきます。
karamarimo

2017/11/16 15:52

返信が遅れてすみません。 $commentId/.write で newData.child('uid').val() というのがおかしいですね。
MOTOMUR

2017/11/16 16:01 編集

ありがとうございます! 全然ここがダメだって気づけませんでした。めっちゃ助かりました。 返信、お気になさらずに、暇な時に返事かあるだけで嬉しい限りです。。。 現在のルールでmypageにて投稿できないのはどこがやばいんですかね。。。 追記:試行錯誤したため、支離滅裂なルールになっていることをお許しください 汗
karamarimo

2017/11/16 16:29 編集

commentCount/.validate と postStar/.write が、新規に書き込む時に false になります。 data.val() は null なので。 前も言いましたが、temps/timestamp に ServerValue.TIMESTAMP を書き込んで取得し、投稿データに入れる、とする必要はありません。 投稿を書き込む時に直接その timestamp キーに ServerValue.TIMESTAMP を指定すれば済むことです。 また、$post/.validate で (newData.hasChildren(['author','body','postComment','postId','postStar','title','uid','timestamp']))||(newData.hasChildren(['author','body','postComment','postId','postStar','title','uid','timestamp','post_image'])) としていますが、前者だけにしても同じでは? hasChildren は 「このキー以外はあってはいけない」というわけではないですよ。
MOTOMUR

2017/11/17 02:29 編集

ありがとうございます!機能復活しました!!!マイページ以外のコードも修正したらとりあえずの機能は完成できます! >前も言いましたが、temps/timestamp に ServerValue.TIMESTAMP を書き込んで取得し、投稿データ  に入れる、とする必要はありません。  投稿を書き込む時に直接その timestamp キーに ServerValue.TIMESTAMP を指定すれば済むことです。 わかりました、一応そのようにやって見ます。 gitに最新のコードを追加したのですが、スター機能を同期的に作用させることを忘れていました。 stateの更新で実現できるかと思ったのですが、うまくいかず。 どのようにしたら良いでしょうか? それと、他人へのコメントのルール設定もうまくいってなさそうなので、こちらは頑張って見ますが、躓いたら、質問させていただくかもしれません。
karamarimo

2017/11/17 06:10

> gitに最新のコードを追加したのですが、スター機能を同期的に作用させることを忘れていました。 stateの更新で実現できるかと思ったのですが、うまくいかず。 どのようにしたら良いでしょうか? どこのコードのことでしょうか?
MOTOMUR

2017/11/17 09:55 編集

starBtn.js とHomeのsearchRefかpostRef4LatestUserの部分ですね。二つの部分です。
karamarimo

2017/11/17 11:17 編集

StarBtn を見ると、表示するスターの状態は props で受け取っているのに ボタンを押すと自分でfirebaseからスターの状態を取得して書き込む、という一貫性のない感じになっています。 個人的にStarBtnは presentational component にして、スター処理は親のコンポーネントでやったほうがいいと思います。 postRef4LatestUser はそのコードが残ってますが、Homeの方も UserCard みたいなコンポーネントを作って、その中でStarBtnを使い、スター処理を UserCard でやるというのがいいと思います。 スターを反映するには2つの方法があります。 1. 自分で state の スター状態を変える 2. value イベントを listen する 後者の方が楽です。なぜならどうせコンポーネントの表示時に once("value") でスター情報を含むデータを取得するので、それを on に変えるだけで動きます。
MOTOMUR

2017/11/17 11:24

なるほど。機能ごとに分割するのがいいのかなと思っていましたが、presentationalとcomponentに分けた方が扱いやすいのですね。そのように修正したいと思います。 コメントのルールについてですが、やはり他人の投稿にはコメント打てません。Homeページのみでしかやって見てませんが。 どこで引っかかってるのでしょうか?
karamarimo

2017/11/17 11:53

> やはり他人の投稿にはコメント打てません うーん分からないですね。 書き込むのコメントのデータは newData.hasChildren(['author','body','commentId','uid','profile_image']) に違反してないですか(すべて揃ってますか?)? あと posts/.write で auth != null としてしまうとカスケードして下の .write が意味を成さないので外したほうがいいと思います。
MOTOMUR

2017/11/17 18:29

とりあえずpost/.writeの件は修正しました。 commentDataの中身は全て揃っていて、 newData.hasChildren(['author','body','commentId','uid','profile_image']) に違反していません。 しかし、$commentが追加されず、また、 postsのcommentCount comments共に、全て追加、更新されません。 一体どこがおかしいのでしょうね。 userPage Home共に同じ状態なので、postsは他人の書き込みNG。$commentに関しては謎のエラーですね。。。 コメントすると出るエラーを一応ここにあげますね。 FIREBASE WARNING: update at / failed: permission_denied これと Repo.js:510 Uncaught (in promise) Error: PERMISSION_DENIED: Permission denied at Repo.js:510 at Object../node_modules/@firebase/database/dist/cjs/src/core/util/util.js.exports.exceptionGuard (util.js:556) at Repo../node_modules/@firebase/database/dist/cjs/src/core/Repo.js.Repo.callOnCompleteCallback (Repo.js:501) at Repo.js:314 at PersistentConnection.js:411 at PersistentConnection../node_modules/@firebase/database/dist/cjs/src/core/PersistentConnection.js.PersistentConnection.onDataMessage_ (PersistentConnection.js:444) at Connection../node_modules/@firebase/database/dist/cjs/src/realtime/Connection.js.Connection.onDataMessage_ (Connection.js:262) at Connection../node_modules/@firebase/database/dist/cjs/src/realtime/Connection.js.Connection.onPrimaryMessageReceived_ (Connection.js:256) at WebSocketConnection.onMessage (Connection.js:157) at WebSocketConnection../node_modules/@firebase/database/dist/cjs/src/realtime/WebSocketConnection.js.WebSocketConnection.appendFrame_ (WebSocketConnection.js:197) at WebSocketConnection../node_modules/@firebase/database/dist/cjs/src/realtime/WebSocketConnection.js.WebSocketConnection.handleIncomingFrame (WebSocketConnection.js:247) at WebSocket.mySock.onmessage (WebSocketConnection.js:144) この二つです。 原因究明よろしくお願いします。
karamarimo

2017/11/17 18:52

謎のエラーというか、Permission denied なので普通にルールに違反したというエラーではないでしょうか。 原因は明日(というか今日)考えてみます。
karamarimo

2017/11/18 04:13

やはり分からないので、試しに、comments の書き込みと posts への書き込みを別々にしてみてください。 そうすれば少なくともどちらが原因か分かります。
MOTOMUR

2017/11/18 04:56

commentsのみだとコメントが追加されるので、postsの中身が問題のようですね。同時なので他で問題があると、全部うまくいかないようです。
karamarimo

2017/11/18 06:50

> 同時なので他で問題があると、全部うまくいかないようです。 それが multi update を使う理由です。一部だけ上手くいくと都合が悪いですよね。 一度、現在データベースにある post のデータがルールに沿っているか確認してもらえますか? 特にコメントを投稿しようとしている post のデータをチェックしてみてください。
MOTOMUR

2017/11/19 06:01

確かにデータ構造がまずかったので、直してコメント投稿して見ましたが、やはりだめですね。
karamarimo

2017/11/19 07:09

では、self.props.post.postId はちゃんと渡ってますか?
MOTOMUR

2017/11/20 01:34

postIdあります。ポストデータもあります。
karamarimo

2017/11/20 08:36

試しに同じようなルールとコードを作ってやってみたんですが、上手くいきました。 なのでルールは大丈夫だと思います。 今一度、送信するデータを確認してください。 (firebase.database().ref().update(updates) のとこで updates を console.log )
karamarimo

2017/11/20 09:51

commentCount が "01" になってますね...。それが原因です。 現在の commentCount が 0 ではなく "0" になっているのでしょう。 なので "0" + 1 を計算すると "01" になったというわけです。 データベースの値を修正してください。
karamarimo

2017/11/20 09:53

js では 文字列と数字を + すると、数字の方が自動的に文字列に変換されて、結合されます。
MOTOMUR

2017/11/20 16:00

ありがとうございます!修正できました! いつの間に文字が保存されていたのでしょう、不思議です。ルールを一時変えて直接修正しました。 現在のコードをGitにあげます。 また新たに困っていることがあるのですが、大規模なアプリケーションになってきており、PostRef4LatestUserでの投稿リスナーのonが重たく、そのファイルのcomponentDidMountで行うと処理が遅くてホームがダウンしてしまいます。(onにしているのはスター情報の同期反応用) そこで、Homeのタブ(Latest Posts)が押された時に、このonを発動するようにしたいのですが、Homeのトリガーで子であるpostRef4LatestUserの関数を動かせられるでしょうか? これが難しければ楽な方法を教えてください。 gitには重たすぎるので、onの関数を外した状態でpushしておきます。
MOTOMUR

2017/11/20 16:07

それと、その影響でなのかはわかりませんが、今まで見れていたコメントも表示不可能です。こちらはもしかしたらLatestUserとStarUserで同じCommentRefを呼び出しているので、衝突している、もしくは同じく処理の重たさで表示できないと思うのですが、解決法あれば教えてください。
karamarimo

2017/11/21 03:55

> gitには重たすぎるので、onの関数を外した状態でpushしておきます。 PostRef4LatestUser.js で on を使用していますが、どこのことでしょうか? https://github.com/MOTOMUR/plz_donation/blob/db52358d09d7f62857b24c89b0d297f8c3584bf0/src/views/components/PostRef4LatestUser.js#L32 > PostRef4LatestUserでの投稿リスナーのonが重たく、そのファイルのcomponentDidMountで行うと処理が遅くてホームがダウンしてしまいます。 関係ないかもしれませんが、componentWillUnmount で off するrefの場所が間違ってますね。 これだとリスナーが解除されないと思います。 https://github.com/MOTOMUR/plz_donation/blob/db52358d09d7f62857b24c89b0d297f8c3584bf0/src/views/components/PostRef4LatestUser.js#L78 on した ref と同じ '/users/'+userId で off する必要があります。
MOTOMUR

2017/11/21 04:20

>PostRef4LatestUser.js で on を使用していますが、どこのことでしょうか? componentDidMountでのそのonのあるfetchPostsDataの読み込みを外したといった方がわかりやすかったですね。すいません。offを直して実行しましたが、やはり重たいままです。 重たくなったタイミングを考えると、スター情報取得のために、そこのコードをonにしてから重たくなっていました。 リスナーの衝突を避けるために、タブを押したタイミングで、postRef4LatestUserのfetchPostUserを呼び出したいのですが、どのように実装できますでしょうか?
MOTOMUR

2017/11/21 04:21

コメントのことを考えると、全てのonリスナーに対して、衝突の対策をしなければならないかもしれません。。。
karamarimo

2017/11/21 04:30

> Homeのタブ(Latest Posts)が押された時に、このonを発動するようにしたいのですが、Homeのトリガーで子であるpostRef4LatestUserの関数を動かせられるでしょうか? それよりも、タブが押された時に lazy に 内容を生成するほうがコードがすっきりすると思います。 自分で LazyTab なるコンポーネントを作ってみてはどうでしょうか。 タブが一度開かれたかどうかを state で保存し、render 内で false なら 内容を表示せず、true なら表示する、とすればいいです。
karamarimo

2017/11/21 04:45

> 全てのonリスナーに対して、衝突の対策をしなければならないかもしれません 衝突とは具体的にどういうことでしょうか? > それと、その影響でなのかはわかりませんが、今まで見れていたコメントも表示不可能です。こちらはもしかしたらLatestUserとStarUserで同じCommentRefを呼び出しているので、衝突している、もしくは同じく処理の重たさで表示できないと思うのですが、解決法あれば教えてください。 コンポーネントはインスタンスごとに状態を持ちますし、firebaseのリスナーも同じデータベースの場所にいくらでも付けれるので、その点では衝突はないと思います。 react devtools などを使って、どの部分まで上手く行っているのか、コメントの取得はできているのかなど、調べてみてください。
MOTOMUR

2017/11/21 06:30

commentRefのコードですがstateのcommentsのデータが [DataSnapshot] 0 : DataSnapshot {node_: ChildrenNode, ref_: Reference, index_: PriorityIndex} length : 1 __proto__ : Array(0) こんなのになってましたね。どのように直したらいいでしょうか?多分.val()じゃなくてsnapshot自体をとってますよね? タブの件は { this.state.tabValue==='a' ?<div></div> :<ul>{ this.state.latest_posts.slice().reverse().map( function(latest_post){ return( <PostRef4LatestUser post={latest_post} myuid={this.state.userId} herfUserPage={this.herfUserPage.bind(this)} /> ) }.bind(this) ) }</ul> } このように修正して、重さは改善されました。(それでもまだ少し重たいですが。)
karamarimo

2017/11/21 07:15

> 多分.val()じゃなくてsnapshot自体をとってますよね? そのとおりです。 https://github.com/MOTOMUR/plz_donation/blob/106150a3c70515132da1fb15bf16428c208401de/src/views/components/CommentRef.js#L40 Promise.all に渡すのは Promise の配列です。 .once は snapshot の Promise を返します。 なので、then(function (comments) {... }) の comments には snapshot の配列が入ります。 .once のコールバックで .val() を return しても意味はありません。 return .....once('value').then(function (snapshot) { return snapshot.val() }) とすればいいと思います。
MOTOMUR

2017/11/21 12:55

なるほどちょっと表現違うだけで全然別の機能になってしまうんですね。 治りました。ありがとうございます。
MOTOMUR

2017/11/23 15:01

現在、困っているのは、commentRef全体に関する事なのですが、コメントを表示した状態で(表示していなくても)新しいコメントを投稿すると、コメント一覧がバグり、過去のコメントが繰り返し表示されてしまい(二重に表示されてしまう)困っています。 どのように直せますでしょうか?
MOTOMUR

2017/11/23 15:26

それと、やはりLatestPostRefが重たすぎて、コメントのfetchなどがうまくいかなかったりするのですが、こちらもコメントが開かれるように押された時に表示するように変えたほうがいいですかね?他にもいい方法ありますか?
karamarimo

2017/11/24 05:35

> やはりLatestPostRefが重たすぎて、コメントのfetchなどがうまくいかなかったりするのですが 動作が重いのは、デバッグモードで走らせているからかもしれません。 デプロイしてみると案外重くないこともあるので、試してみてください。
karamarimo

2017/11/24 05:55

https://teratail.com/questions/101811 これを見てて思いましたが、onClick などのハンドラーを動的に生成すると、子コンポーネントを無意味に再描画させてしまうのでパフォーマンスを低下させる一因になります。 例えばここですね。 https://github.com/MOTOMUR/plz_donation/blob/821262cf5d96654e38bc9c18d91f1fbeb23c973b/src/views/components/PostRef4LatestUser.js#L120 https://github.com/MOTOMUR/plz_donation/blob/821262cf5d96654e38bc9c18d91f1fbeb23c973b/src/views/components/PostRef4LatestUser.js#L154 メソッドの定義の方で arrow function を用いるようにすれば bind は必要ないですね。
MOTOMUR

2017/11/24 18:48

>forEach の外で setState や fetchComments をすればいいのでは? 単純にミスっていましたね。しかし、少々修正してallなんとかは消して直接DidMount、に突っ込みましたが componentDidMount(){ const self = this this.fetchCommentID(function() { self.fetchComments() }) } 原因がコールバック不良なのかなと思いましたが、 fetchCommentID() { const self = this const commentsId = []; const postId = this.props.postId var onValueChange = firebase.database().ref('/posts/' + postId + '/postComment/comments').on('value', function(snapshot) { snapshot.forEach(function(childSnapshot) { commentsId.push(childSnapshot.key) }) self.setState({commentsId: commentsId}); }) this.setState({ off_postComments:onValueChange }) } ここのselfのsetStateでfetchpostsをコールバックしないとコメントが表示されず、 コールバックすると、コメント多重(前のコメント一覧+現在のコメント一覧)してしまいます。 他にも問題点あるかもしれませんが、とりあえず、didmountでのコールバック不良の原因がわかりません。firebaseとの兼ね合いで特殊なルールでもあるのでしょうか?
MOTOMUR

2017/11/24 18:52

>デバックモードだからかもしれません。 それも一応考慮してdeploy後動かしてみていたのですが、軽減は微量でした。 現状、描写関係が重たいのかなと推測しています。 onClickのハンドラー生成でも重たくなってしまうのですね。 arrow functionが何かは現在わかっていないので勉強しようと思います。
karamarimo

2017/11/25 02:45

> ここのselfのsetStateでfetchpostsをコールバックしないとコメントが表示されず、 コールバックすると、コメント多重(前のコメント一覧+現在のコメント一覧)してしまいます。 そもそも this.fetchCommentID(function() { self.fetchComments() }) とコールバックを渡しても fetchCommentID では使われていないので意味がないですね。 fetchCommentID(calllback) { ... self.setState({commentsId: commentsId}, function () { if (callback) callback(); }) ... } とすればいいと思います。 コメントが2重に表示される原因は分かりますか?
MOTOMUR

2017/11/25 04:46

>コメントが2重に表示される原因は分かりますか? const commentsIdが変化前に受け取った情報を持っているのに、初期化せずに次の情報がプッシュされているからだと思います。あってますか? constの初期化は commentsId=[ ]とすればできると思ったのですが、これだとダメなのですね。初期化と調べてみたのですが、結果は見つからなかったのですが、どのようにconst配列の初期化を行うのでしょうか?
karamarimo

2017/11/25 05:08

> constの初期化は commentsId=[ ]とすればできると思ったのですが、これだとダメなのですね いや、それ自体は間違いではありません。問題は [] を代入するタイミングです。 現状では fetchCommentID が呼び出されたときにだけ [] が代入されます。 その後、value イベントが発生するたびに id が push されていきます。 value イベントが発生するたびに [] に戻す必要がありますよね?
MOTOMUR

2017/11/25 05:30

初期化のタイミングを勘違いしていました。onリスナーにて発火された場合、外側のconst commentsIdに関係なくonのみ中身が動いてしまうことに気づきませんでした。ありがとうございます。 arrow functionについて調べてみていたのですが、疑問があるので質問させていただきます。 一応一通り記事を読んできましたが、arrowすることによってグローバルにthisを指定できることはわかりました。 ただかなり曖昧な理解でコードを書いている部分が多数あったので聞きたいのですが、 まず関数の呼び出しも関数自体も同じファイル内での処理の場合、 例えば handleCange(){・・・} onClick={this.handleCange } この場合 handleCange=()=>{・・・} onClick={this.handleCange } この場合 handleCange(){・・・} onClick={()=>this.handleCange } この場合 handleCange=()=>{・・・} onClick={()=>this.handleCange } この場合。 の4つの呼び出し方があると思うのですが、(onClickの呼び出しに引数を入れてない部分もありますが、どの記述であれば引数を渡せるパターンなのかということが曖昧なので省略しています。) それぞれ何ができるかといった違いがよくわかっていません。onClick={ }内にarrowを入れると引数を関数に渡せるのかなくらいの認識です。それぞれの記述の違いでできることの差は何でしょうか。 また、自分の経験からの認識で話しますが、ファイルが別の関数を呼び出すとき((onClickじゃない自作の関数)={this.props. ・・・.bind(this) })、またはrender内のfunctionにbind(this)を使わないとうまくいかないという認識でしたが、そもそもどんなときにthisの指定が外れてしまうのでしょうか? 教えていただけますでしょうか。
karamarimo

2017/11/25 07:49

()=>this.handleCange ではなく ()=>this.handleCange() ですね。 handleChange の中で this を使わないのであれば、どのパターンでも構いません。 使うならば handleCange=()=>{・・・} onClick={this.handleCange } か handleCange(){・・・} onClick={()=>this.handleCange() } か handleCange(){・・・} onClick={this.handleCange.bind(this)} になります。 handleCange=()=>{・・・} onClick={()=>this.handleCange() } は冗長です。 arrow function 内では this は、自身の外での this と同じものを指します。 handleCange=()=>{ this.state... } とすれば、この this は handleChange の外での this 、つまり component のインスタンスを指します。 arrow function を使わないと、this が何を指すかは呼び出し方に依存します。 なのでさらに arrow function で包むか、bind(this) をするなどして、component のインスタンス に this を束縛する必要があります。 まあ詳しいことは調べてください。 適当にピックアップした記事 https://qiita.com/mejileben/items/69e5facdb60781927929 先程申し上げた通り、パフォーマンスを考えると handleCange=()=>{・・・} onClick={this.handleCange } がベストになります。 引数を渡したいのであれば、定義のところで引数を書けばいいだけです。 handleCange=(parameter1, parameter2, parameter3)=>{・・・}
karamarimo

2017/11/25 07:57

このページがかなり重くなってきたので、github で issue を作って質問を受ける、というのでもいいでしょうか?
MOTOMUR

2017/11/25 07:59

githubにそういう機能があったのですね。 長らく回答ありがとうございました。今後はそちらでよろしくお願いします!
guest

0

おふたがた,大変お疲れ様です…
もし大変苦戦するようであれば,思い切ってReactではなくVue.jsに乗り換えてしまうのもありかもしれません.

Vue.jsは後発なだけあってより習得しやすく手軽に使え,しかし拡張性も公式でサポートされています.
すぐに開発を初められるツール群も公式で提供されていたり,*.vueという,html/js/cssを機能単位で1ファイルにまとめられる機能などもミソです.
また,日本語ドキュメントも充実していますし,Firebaseとの相性も良いです.

投稿2017/10/24 08:11

Yatima

総合スコア1159

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

karamarimo

2017/10/24 09:07

アドバイスありがとうございます。 確かにVuejs公式のVueFireは便利そうですね。 しかしMOTOMURさんはすでにたくさんのコンポーネントを作っておられるので今から移行するのは面倒かもしれません...。
MOTOMUR

2017/10/24 12:33

アドバイスありがとうございます。 今回のサイトを移行するのは大変厳しい状況かと思います。 今後別のサイト作成の機会があったらvue導入を検討してみます。貴重なご意見ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問