KAEDE Hack blog

JavaScript 中心に ライブラリなどの使い方を解説する技術ブログ。

React Component の実用

why

class Componentを使用したReactでの動的サイト製作の記事

wesのReact Firebase Note App Tutorialを参考にして作っている.

index

index.js

import App from './App';
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

基本的にはindex.htmlにlinkがあるindex.jsが起点になる

そのindex.jsでComponentの読み込み地点となる App Component(以下App)をimportし,get...Idでrenderする

App

App.jsが基本的にCotainerになる.
このApp.js自体もComponentであり,としてindexでimportされている. 今回のアプリでのデータnoteを描画する<Note/>
noteを追加するためのform, <NoteForm/>を読みこむ.

Componentはclassを使い,基本的にこのように書く

App.js

class App extends Component {
  render() {
    return (
      <div>
        <h1>React and Firebase To-Do List</h1>
        <Note />
      </div>
    );
  }
}

file nameと同じclass nameをとり,render() { return( ) }のナカに 一つのdivを作り,そのナカにhtmlを書く.

Appではこのように他のComponentを<Hoge/>と呼び出す

jsなので当然他のファイルから使うためには Note.jsxでexport default Noteをして

App.jsでもNote.jsxを使うために

import Note from './Note/Note';

でexportされたComponentをimportする必要がある


そして今回はCRUDのReadの役割を果たすリスト表示のNote.jsx

Note.jsx

import React, { Component } from 'react';

import './Note.css';
import PropTypes from 'prop-types';

でまずReact, Component, css, PropTypesをimportする.

prop-typesは型を判定してくれる.(後述)

class Note extends Component {
  constructor(props) {
    super(props);
    this.message = 'Hello from Note Component';
  }

ここではconstructorを書き,superをして, このComponentのなかにmessageを直接定義する.

  render(props) {
    return(
      <div>
        <h1>{this.message}</h1>
      </div>
    )
  }
}
export default Note;

本当はstateを使う.

今回はrenderにもpropを食わせてpropsの値を表示する.

そうするとlocalhost3kでNote.jsxのComponent fileに書かれた内容が App.jsで呼び出されてこうやって表示される

f:id:kei_s_lifehack:20200413192934p:plain

NoteForm.jsx

前回のはstateの中身を各自cssを当てて表示するだけなので, stateにGUIから入れるためのinputのを新たに作成する

Appのfooter will go here...をに交換, NoteForm.jsxを作る.
react, component, cssをimportし,constructorにemptyなstateを作り,
renderにinputとbtnを設ける.

NoteForm.jsx > render()

  render(){
    return(
      <div className = "formWrapper">
        <input 
          className="noteInput"
          placeholder = "Write a new note..."
        />
        <button className="noteButton">Add Note</button>
      </div>
    )
  }

そしてApp.jsで使えるようにNoteFormをexport default NoteForm;してimportする.

これでinputとaddが表示された

f:id:kei_s_lifehack:20200414082149p:plain

input内のvalueとread-only

NoteFormのinputのvalueを追加し,stateの中身を{}で埋め込む

        <input 
          className="noteInput"
          placeholder = "Write a new note..."
          value =  {this.state.newNoteContent}
        />

これでこのComponentのstateのnewNoteContentがinputに 最初の読み込みに表示される.

f:id:kei_s_lifehack:20200414085710p:plain

だがこのままだとRead-Only Propertyだと警告をされる

index.js:1 Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field.

なのでonChangeを書き込む必要がある.
valueと同じように{}bracketで埋め込み, 関数のtriggerにする.

onChange

constructorのbracketの閉じた後にhandle関数を仕込む.

  handleUserInput(e) {
    this.setState({
      newNoteContent  : e.target.value,
    })
  }

これをrenderのinputのなかにonChangeで

onChange = {this.handleUserInput}

と仕込むと,このinputの中が変更されるたびにhandleUserInputがその中の valueで起動する.

だがこのままではCannot read property 'setState' of undefinedなので
constructorの中にbindする.そうすると使える.

  constructor(props) {
    super(props);
    this.state = {
      newNoteContent: 'New Note Content',
    }
    this.handleUserInput = this.handleUserInput.bind(this);
  }

ここでhandleUserInputにconsole.logでthis.stateを吐かせると

f:id:kei_s_lifehack:20200415164306p:plain

変更されるたびに中身を吐き出してくれる.

これがstateと連携したonChangeのhandleだ

これでGUIでinputからstateを変更できるようになる.

次は同じようにして,btnにonClickを仕込む.


btnにonClickをつけwriteNoteのtriggerにする

btnも同じようにrenderのタグ内に仕込む

        <button 
          className="noteButton"
          onClick = {this.writeNote}
        >Add Note</button>

propsには{}を使い,関数名の後に()はつけない.

setState({})を使ってstateを操作する関数を追加する.

  writeNote(){
    this.setState({
      newNoteContent: '', // empty input box
    })
  }

constructorの後にwriteNoteのfuncを書く.
とりあえずnewNoteContentをemptyにする処理を入れておく

    this.writeNote = this.writeNote.bind(this);

そして先ほどと同じようにbind(this)する. すると押すとinputの中にvalueで紐づけられているnewNoteContentというstateが
空のstringになるwriteNoteが起動するbuttonが完成する.


考えたらこれは動作テストで入れてる処理ではなく,「DBへの追加処理が発生したら 入力欄を空にする」という当たり前に必要なMethodだった.これがないからFirebaseとjQで作った家計簿アプリは「btnを押した時に追加した手応えがなくて連続押して,二つ同じものが入ってしまう」ということが起こりがちだったんだな

(悪い例: https://moneylog-e7c4b.firebaseapp.com/ )


NoteFormとAppの連携

AppでAppのstateにpushするaddNote()を作成してNoteFormのwriteNoteで使用させる.

ここではNoteForm Componentに作成したwriteNoteのなかにaddNoteを使用し,
そのaddNoteはApp.jsに書く.NoteFormのstateではなく,Appのstateに入れるためだ.

  addNote(note) {
    this.state.notes.push(note)
  }

これでstateの中のnotesというarrayを引数でとったnoteに追加する.

    this.addNote = this.addNote.bind(this);

handleやwriteの関数と同じように,Appのconstuctorのナカでbindする.

また,これだけではNoteFormで使えないので,
renderにある

          <NoteForm addNote={this.addNote}/>

という形addNoteの関数をNoteFormに渡す.

NoteForm use addNote

そしてNoteFormで使う.

  writeNote(){
    this.props.addNote(this.state.newNoteContent);
    this.setState({
      newNoteContent: '', // empty input box
    })
  }

Appのrenderの中で渡されたthis.addNoteはthis.propsの中にはいるので取り出し
ここNoteFormのstateの中にあるnewNoteContentをaddNoteを使って
Appのstateのnotesにpushする.

これでaddNoteにconsole.logを仕込んで見るとnotesは一つ増えているのが見える

0: {id: 1, noteContent: "Note 1 here"}
1: {id: 2, noteContent: "Note 2 here"}
2: "New Note Content"
length: 3
__proto__: Array(0)

だが画面の更新はされない.

previous noteを挟む

ここでaddNoteの処理に一つ噛ませる

  addNote(note) {
    const previousNotes = this.state.notes;
    previousNotes.push({
      id: previousNotes.length + 1,
      noteContent: note,
    });

    this.setState({
      notes: previousNotes,
    })
    console.log(this.state.notes);
  }

previousNotesという変数にstateのnotesを入れてから
その変数に引数でとった追加する内容であるnoteを
noteContentとして, さらに現在の追加さき配列notesの次の添字をidとして
pushする. 最後にnotesにpreviousNotesの内容を入れる. stateに直接値を入れては行けないからsetState({})を使用する.    回りくどく見えるが,stateを更新する処理を別の処理と分割するためにpreviousNote介する.長くなってきた時にわかる.

さらにreact特有のkeyを渡す必要が出てくる.これがないとエラー.

render, give state id, content, key,

だからApp.jsのrenderでnotesをmapで展開して,Note Componentにkey, id, Contentを渡すことで描画している.

これでlocalなReactのstateにinputを介して追加して,それを即座に更新して 表示することができるようになった.

conclude

おさらいすると,<App /> のstateにidとcontentのobjをもつ配列notesを作成.
renderではそのstateにあるnotesをmapで展開し,id, key = id, contentを
<Note />に渡す.<Note />ではconstructorでpropsから受け取り,
renderでcontentを表示する.そこにcssがかかってカードになる.また
propTypesによって型がstringかチェックが入る.

追加, ADD編ではaddNoteという関数をAppのconstructorの後に作り,
stateのnotesに配列の長さ+1でid,引数をcontentにpushする.
それを変数を介してsetStateし,この関数addNote()を<NoteForm />に私,
propから受け取り,inputのvalueにstate newNoteContentを組み込み,
onChangeでhandleしてe.target.valueからnewNoteContentに渡し,
btnのonClickで先ほど<App />からもらったaddNoteをpropから使って
addNoteにnewNoteContentを渡し,今inputのvalue に入っている
newNoteContentを空にする.

ReactはここでRenderが更新される.

これでCRUDのRead, Create, Updateの処理までができた.

これではbrowserをrefleshした時にstateに入れたdataも消えてしまうから,Firebaseに保存し,起動した時にFirebaseから値を取ってくる物にする.

その後,moneylogのようにhttpsでアクセスできるようにfirebase hostingを活用する.