Progate終了レベル+αの知識でReactアプリを実装

ProgateでReactの勉強をしました。
Progate上で実装した機能をローカル環境で再現して理解を深めようと思います。

##作成する機能
- 複数のコンポーネントを組み合わせて1枚のページを作成
- ボタンをクリックしたらテキストの中身が切り替わる
- モーダル
- フォーム
    - 送信をクリックするとフォーム部分だけ表示が変わる
    - 何も入力されていないとエラーを出す

##creat-react-appのインストール
```zsh
$ npm i -g create-react-app
```

##プロジェクトの初期化
```zsh
$ create-react-app progate-local
$ cd progate-local
$ rm -rf src
$ mkdir src src/components
$ touch src/index.js src/components/App.js
```
```src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(<App />, document.getElementById('root'));

```
```src/components/App.js
import React from 'react';

class App extends React.Component {
  render() {
    return (
      <div>
        <p>Hello World</p>
      </div>
    );
  }
}

export default App;
```

##複数のコンポーネントを組み合わせてページを作成
```zsh
$ touch src/components/Header.js src/components/Footer.js
```
```jsx:src/components/App.js
import React from 'react';
import Header from './Header';
import Footer from './Footer'

class App extends React.Component {
  render() {
    return (
      <div>
        <Header />
        <Footer />
      </div>
    );
  }
}

export default App;
```
```jsx:src/components/Header.js
import React from 'react';

class Header extends React.Component {
  render() {
    return (
      <header>
        <h1>Header!!!</h1>
      </header>
    );
  }
}

export default Header;
```
```jsx:src/components/Footer.js
import React from 'react';

class Footer extends React.Component {
  render() {
    return (
      <footer>
        <small>Footer!!!</small>
      </footer>
    );
  }
}

export default Footer;
```

##ボタンクリックでテキストが切り替わる
```zsh
$ touch src/components/ChangeText.js
```
```jsx:src/components/ChangeText.js
import React from "react";

class ChangeText extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: '茂野吾郎'
    }
  }
  changeName(name) {
    this.setState({
      name: name
    })
  }
  render() {
    return (
      <div>
        <h2>こんにちは{this.state.name}さん</h2>
        <button onClick={ () => this.changeName('茂野吾郎') }>茂野吾郎に変更</button>
        <button onClick={ () => this.changeName('佐藤寿也') }>佐藤寿也に変更</button>
      </div>
    );
  }
}

export default ChangeText;
```
```diff:src/components/App.js
import React from 'react';
import Header from './Header';
import Footer from './Footer'
+ import ChangeText from './ChangeText'

class App extends React.Component {
  render() {
    return (
      <div>
        <Header />
+         <ChangeText />
        <Footer />
      </div>
    );
  }
}

export default App;
```

###ポイント
- `ChangeText.js`というコンポーネントを作成
- `name`という名前のstateを定義
- `name`の値を出力する
- 引数にとった文字列で`name`を更新するメソッド(`changeName`)を作成
- ボタンを作成し、クリック時に`changeName`メソッドを発火

##モーダルを作成
```zsh
$ touch src/components/Modal.js
$ touch src/modal.css
```
```jsx:src/components/Modal.js
import React from "react";
import '../modal.css'

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isModalOpen: false,
    };
  }
  toggleModal() {
    this.setState({
      isModalOpen: !this.state.isModalOpen,
    });
  }
  render() {
    let modal
    if(this.state.isModalOpen){
      modal = (
        <div className='modal'>
          <div className='modal_content'>
            <p>モーダル!!!</p>
            <button onClick={ ()=> { this.toggleModal() } }>閉じる</button>
          </div>
        </div>
      )
    }
    return(
      <div>
        <button onClick={ () => { this.toggleModal() } }>モーダルを開く</button>
        {modal}
      </div>
    )
  }
}

export default Modal;
```
```src/modal.css
.modal {
  background: rgba(0,0,0,0.7);
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
}

.modal_content {
  background: #fff;
  padding: 20px;
  width: 70%;
  height: auto;
  margin: 100px auto 0;
}
```
```diff:src/components/App.js
import React from 'react';
import Header from './Header';
import Footer from './Footer'
import ChangeText from './ChangeText'
+ import Modal from './Modal'

class App extends React.Component {
  render() {
    return (
      <div>
        <Header />
        <ChangeText />
+        <Modal />
        <Footer />
      </div>
    );
  }
}

export default App;
```

###ポイント
- `Modal.js`というコンポーネントを作成
- `isModalOpen`という、真偽値をとるstateを定義
- `modal`という変数を定義し、`isModalOpen`がtrueの時だけ値(html)が入るようにする
- `isModalOpen`の値を変更するメソッド(`toggleModal`)を作成
- クリック時に`toggleModal`が発火するボタンを、モーダル内とモーダル外にそれぞれ設置する

##モーダルを繰り返し表示
```jsx:src/components/App.js
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
import ChangeText from "./ChangeText";
import Modal from "./Modal";

class App extends React.Component {
  render() {
    const playerList = [
      {
        position: "ピッチャー",
        name: "茂野吾郎",
      },
      {
        position: "キャッチャー",
        name: "佐藤寿也",
      },
      {
        position: "ファースト",
        name: "ギブソンjr",
      },
    ];
    return (
      <div>
        <Header />
        <ChangeText />
        <h3>モーダル</h3>
        {playerList.map((playerItem, index) => {
          return (
            <Modal
              key={index}
              position={playerItem.position}
              name={playerItem.name}
            />
          );
        })}
        <Footer />
      </div>
    );
  }
}

export default App;
```
```jsx:src/components/Modal.js
//省略
  render() {
    let modal
    if(this.state.isModalOpen){
      modal = (
        <div className='modal'>
          <div className='modal_content'>
            <p>{this.props.position}</p>
            <p>{this.props.name}</p>
            <button onClick={ ()=> { this.toggleModal() } }>閉じる</button>
          </div>
        </div>
      )
    }
    return(
      <div>
        <button onClick={ () => { this.toggleModal() } }>{this.props.position}</button>
        {modal}
      </div>
    )
  }
}

export default Modal;
```

###ポイント
- `App.js`内に配列(`playerList`)を定義する
- `playerList`に`map`メソッドを適用し、ループの中でModalコンポーネントを呼び出す
- 呼び出す時に変数を受け渡す
- 受け渡された値は`Modal.js`内で`this.props.hoge`で受け取る

##フォームを作成
```zsh
$ touch src/components/Form.js
```
```jsx:src/components/Form.js
import React from "react";

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isSubmitted: false,
      email: "",
      hasEmailError: false,
      content: "",
      hasContentError: false,
    };
  }
  handleEmailChange(event) {
    const inputValue = event.target.value;
    const isEmpty = event.target.value === "";
    this.setState({
      email: inputValue,
      hasEmailError: isEmpty,
    });
  }
  handleContentChange(event) {
    const inputValue = event.target.value;
    const isEmpty = event.target.value === "";
    this.setState({
      content: inputValue,
      hasContentError: isEmpty,
    });
  }
  handleSubmit() {
    this.setState({
      isSubmitted: true,
    });
  }
  render() {
    let emailErrorText;
    if (this.state.hasEmailError) {
      emailErrorText = <p>メールアドレスを入力してください</p>;
    }
    let contentErrorText;
    if (this.state.hasContentError) {
      contentErrorText = <p>問合せ内容を入力してください</p>;
    }
    let contactForm;
    if (this.state.isSubmitted) {
      contactForm = <p>送信完了</p>;
    } else {
      contactForm = (
        <div>
          <p>メールアドレス</p>
          <input
            type="text"
            value={this.state.email}
            onChange={(event) => {
              this.handleEmailChange(event);
            }}
          />
          {emailErrorText}
          <p>問合せ内容</p>
          <textarea
            value={this.state.content}
            onChange={(event) => {
              this.handleContentChange(event);
            }}
          />
          { contentErrorText }
          <input
            type="submit"
            value="送信"
            onClick={() => {
              this.handleSubmit();
            }}
          />
        </div>
      );
    }
    return <div>{contactForm}</div>;
  }
}

export default Form;
```
```diff:src/components/App.js
import React from "react";
import Header from "./Header";
import Footer from "./Footer";
import ChangeText from "./ChangeText";
import Modal from "./Modal";
+ import Form from "./Form";

class App extends React.Component {
  render() {
省略
    return (
      <div>
        <Header />
        <ChangeText />
        <h3>モーダル</h3>
        {playerList.map((playerItem, index) => {
          return (
            <Modal
              key={index}
              position={playerItem.position}
              name={playerItem.name}
            />
          );
        })}
+        <Form />
        <Footer />
      </div>
    );
  }
}

export default App;
```

###ポイント
- `Form.js`というコンポーネントを作成
- `isSubmitted`、`content`、`hasContentError`というstateを定義
- form入力欄のvalueに`content`の値を設定する
- `contactForm`という変数を定義し、`isSubmitted`の値に応じて表示を切り分ける
- `isSubmitted`の値を変更するメソッド(`handleSubmit`)を定義し、送信ボタンクリック時に発火させる
- 入力欄に入力された値に応じてstateを変更するメソッド(`handleContentChange`)を作成
- 入力欄の値が変更された時、`handleContentChange`を発火させる
- `ContentErrorText`という変数を定義し、`hasContentError`がtrueの時だけ値(html)が入るようにする
- 入力欄の下に`ContentErrorText`を出力
- email入力欄に関しても、contentと同様の設定をする

##参考
- [React | プログラミングの入門なら基礎から学べるProgate[プロゲート] |](https://prog-8.com/languages/react)
- [React 開発環境構築 - Qiita](https://qiita.com/rspmharada7645/items/25c496aee87973bcc7a5)