Day-to-day the memorandum

やったことのメモ書きです。

Reactを触ってみた

Reactを触ってみました

はじめに

zypressen.hatenablog.com
前回の記事で作ったAPIを使用して、以下のようなデータを表示したりするWebページをReactで作ってみたいと思います。
f:id:ZYPRESSEN:20181016010836g:plain

Webページの作成

Node version

$ node --version
v8.12.0

npm version

$ npm --version
6.4.1

yarn version

$ yarn --version
1.5.1

フォルダ構成

f:id:ZYPRESSEN:20181013145021p:plain

Reactのアプリを作成

以下ドキュメントに書いてある通りCreate React Appで作っていきます
https://reactjs.org/docs/create-a-new-react-app.html#create-react-app
以下のコマンドでReactのアプリを作ってくれます。

$ npx create-react-app react-demo

axiosの追加

今回はAPIにリクエストを送るのにaxiosというライブラリを使いたいと思います。
GitHub - axios/axios: Promise based HTTP client for the browser and node.js
以下のコマンドで追加します。

$ cd react-demo
$ yarn add axios

以下が最終的なpackage.jsonです。

{
  "name": "react-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-scripts": "1.1.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Component

ReactはいくつかComponentを組み合わせてページを作っていきます。
今回つくるページのComponentは以下のようにしてみました。
f:id:ZYPRESSEN:20181016004320p:plain

Context

続いて今回はContextを使ってみました。
Context – React
データをほかのComponentに受け渡すときは、propsを使い「親→子→孫」のようにバケツリレーを行っていましたが、Contextを使うことによってどのComponentからも参照できるようになります。
以下のようにContextを作ります。

import { createContext } from 'react';

const PersonContext = createContext();
export default PersonContext;

App.js

create-react-appで作った場合、すでにApp.jsがあると思います。
このファイルを以下のように変更していきます。

import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import PersonContent from './components/PersonContent';
import PersonContext from './context/Person';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      personList: [], // APIにリクエストを送って取得したPersonのリストを保持
    }
  }

  componentDidMount() {
    this.getPersonList(); // 初期表示をする
  }

  getPersonList() {
    axios
      .get("http://localhost:8080/person") // URLを指定して
      .then(response => { // 成功したら
        const data = response.data;
        this.setState({ // stateに取得したデータをセット
          personList: data
        })
      })
      .catch(error =>{ // Errorが起きたら
        console.log(error);
      })
  }

  render() {
    return (
      <div className="App">
        <PersonContext.Provider value={{
          personList: this.state.personList,
          getPersonList: () => this.getPersonList()
        }}>
          <PersonContent />
        </PersonContext.Provider>
      </div>
    );
  }
}

export default App;

getPersonList()でaxiosを使ってAPIにリクエストを送り、返ってきたデータをstateにセットしています。
renderのところで表示するComponentなどを書いていきます。
ここで他のComponentでデータを参照できるようにContextを使っています。
PersonContext.Providerのvalueでデータを指定します。
今回はpersonListとgetPersonList()を指定しています。

PersonContent,js

なにかと書いてありますが、PersonRegisterとPersonListの2つのComponentで構成しています。

import React, { Fragment } from 'react';
import PersonRegister from './PersonRegister';
import PersonList from './PersonList';
import PersonContext from '../context/Person'

const PersonContent = () => (
  <PersonContext.Consumer>
    {({getPersonList}) => ( 
      <Fragment>
        <PersonRegister getPersonList={getPersonList} />
        <PersonList />
      </Fragment>

    )}
  </PersonContext.Consumer>
);

export default PersonContent;

とりあえずPersonListを見ていきます。

PersonList.js

このComponentはPersonのリストを表示します。
PersonContext.ConsumerでApp.jsのPersonContext.Providerのvalueで設定した値が使えます。

import React from 'react';
import PersonContext from '../context/Person';

const PersonList = () => (
   <PersonContext.Consumer>
    {({personList}) => ( // PersonContext.Providerで設定した値
      <div>
        <table>
          <thead>
          <tr>
            <th>name</th>
            <th>age</th>
          </tr>
          </thead>
          <tbody>
          {personList.map(person => (
            <tr key={person.id}>
              <td>{person.name}</td>
              <td>{person.age}</td>
            </tr>
          ))}
          </tbody>
        </table>
      </div>
    )}
  </PersonContext.Consumer>
);

export default PersonList;

PersonRegister.js

PersonContentに戻ります。
PersonRegisterのrender()以外でgetPersonList()を使いたかったので、propsで渡しています。
PersonList同様、PersonContext.ConsumerでgetPersonList()を使えるようにしています。

const PersonContent = () => (
  <PersonContext.Consumer>
    {({getPersonList}) => (  // PersonContext.Providerで設定した値
      <Fragment>
        <PersonRegister getPersonList={getPersonList} />
        <PersonList />
      </Fragment>

    )}
  </PersonContext.Consumer>
);

続いて、データを登録するComponentです。
テキストボックスに値を入れるとstateに値をセットするようにしています。
送信ボタンを押すとAPIにPOSTリクエストを送ってデータを保存します。
送信後テキストボックスの値をクリアするために、formにrefを設定してリクエストを送った後にreset()を呼んでいます。

import React, { Component } from 'react';
import axios from 'axios';

class PersonRegister extends Component {
  constructor(props){
    super(props);
    this.form = React.createRef(); // ref create
    this.state = {
      name: "", // フォームのnameの値
      age: "" // フォームのageの値
    }
  }

  handleChange(event) {
    this.setState({
      [event.target.name]: event.target.value
    })
  }

  handleSubmit(event) {
    event.preventDefault(); // 伝播を止める
    const data = {name: this.state.name, age: this.state.age};
    axios
      .post("http://localhost:8080/person", data)
      .then(response => {
        this.props.getPersonList(); // リストを更新する
        this.form.current.reset();  // フォームをリセットする
      })
      .catch(error => {
        console.log("error: ", error)
      });
  }

  render() {
    return (
      <div className="PersonRegister">
        <form onSubmit={e => this.handleSubmit(e)} ref={this.form}>
          <label htmlFor="name">name</label>
          <input id="name" name="name" type="text" onChange={e => this.handleChange(e)} />
          <label htmlFor="content">age</label>
          <input id="age" name="age" type="text" onChange={e => this.handleChange(e)} />
          <button type="submit">submit</button>
        </form>
      </div>
    );
  }
}

export default PersonRegister;

実行してみる

前回の記事で作ったAPIを起動しておきます。
以下コマンドを実行するとブラウザが開きます。

$ yarn start 

自動で開かない場合は以下URLにアクセスしてみてください。
http://localhost:3000
実はこのままアクセスしてもListが表示されず、データの登録ができないです。
chromeなどのDeveloper toolsのconsoleを見ると、以下のようなエラーが出ていると思います。

Failed to load http://localhost:8080/person: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

前回作ったAPIのほうで、Access-Control-Allow-Originの設定をしていないため、ブラウザにより通信を拒否されてしまいます。

CORS対応

ということで前回作ったAPIのほうの修正をします。
以下のURLを参考にクラスを1つ追加して設定していきます。
https://docs.spring.io/spring-framework/docs/5.1.0.RELEASE/spring-framework-reference/web-reactive.html#webflux-cors-global

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();

        config.setAllowCredentials(true); // cookie
        config.addAllowedOrigin("*"); // domain
        config.addAllowedHeader("*"); // header
        config.addAllowedMethod("*"); // http method

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

APIを再起動して、ブラウザを更新するとListが表示され、データを登録もできると思います。

さいごに

githubリポジトリです。
今後の記事に、このリポジトリを使いたいのでv1.0というTagをうっておきました。
github.com
WebFluxのほうはv2.0というTagをうっておきました。
github.com