2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React + ReduxでAPI叩いた結果を画面に反映する。(おまけ程度のMaterial UI)

Posted at

概要

タイトルの構成でログイン画面もどきを作りたい。
一旦暗号化とかエラーハンドリングとかそういうのは後回し。:mask:

今回はフロントエンド側の説明がメインです。

自分のコードをチームメンバーに解説するための資料&備忘録になるので間違い等あるかもしれません。
ご親切な方、もしよろしかったらご指摘ください。:bow_tone1:

構成

バック  : Express / Node.js
フロント : React + Redex + Material UI

コンソール2つ用意してどちらもサーバを起動してる。
ポートはよしなに。。。

##Redux
Reduxに関する解説はこちらの記事を参照させてください。
Redux/react-reduxチートシート
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解

ソースの解説

React側のプロジェクト直下のファイル構成は以下の通り
スクリーンショット 2019-09-04 13.58.56.png


###index.js

src/index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import reducer from './reducers'
import App from './containers/App'

const middleware = [ thunk ]

if (process.env.NODE_ENV !== 'production') {
  middleware.push(createLogger())
}

const store = createStore(
    reducer,
    applyMiddleware(...middleware)
)

render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById('root')

public/index.htmlの<div id="root"></div>にrenderしている。
reducerをインポートし、createStoreを行い<Provider store={store}>でReactとReduxを連携している。

createStoreの記述内で非同期処理を行うためのredux-thunkとログ吐き出すためのredux-loggerの2つのmiddleaareを結びつける。


###containers/App.js

containers/App.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Login from '../components/Login'

class App extends Component {
    static propTypes = {
        value: PropTypes.string.isRequired,
        dispatch: PropTypes.func.isRequired
    }

    render() {
        const { value } = this.props
        return (
            <div>
                <div>
                    <Login value={value} />
                </div>
            </div>
        )
    }
}

const mapStateToProps = state => {
    const { value } = state.value || {
        value: "please input"
    }
    return {
        value: value
    }
}

export default connect(mapStateToProps)(App)

mapStateToPropsではstateをpropsとして利用できるように設定します。
connectの引数としてmapStateToPropsとAppを設定することでReactとReduxが連携されています。
今回はここでvalueのデフォルト値を設定しています。

mapStateToPropsで定義したvalueは上述した通り、Reduxで管理され、
const { value } = this.propsを介して、Login.jsのコンポーネントに渡すvalueを取り扱います。


###components/Login.js

components/Login.js
import React from 'react'
import PropTypes from 'prop-types'
import { Button, TextField } from "@material-ui/core";
import { fetchLogin } from "../actions";
import { connect } from "react-redux";
import "../css/Login.css"

export class Login extends React.Component {
    constructor(props) {
        super(props);
        this.state = { email: "", pasword: ""};
    }

    fetchLogin = (email, password) => {
        this.props.fetchLogin(this.state.email, this.state.password);
    };
    emailChange = (e) => {
        this.setState({
            email: e.target.value
        });
    }
    passwordChange = (e) => {
        this.setState({
            password: e.target.value
        });
    }

    render() {
        console.log(this.value)
        return (
            <div className="loginForm">
                <p className="loginTitle">
                    {this.props.value}
                </p>
                <ul className="loginFormDetail">
                    <br />
                    <TextField
                        label="e-mail"
                        name="email"
                        value={this.state.email}
                        onChange={this.emailChange}
                        margin="normal"
                    />
                    <br />
                    <TextField
                        label="password"
                        name="password"
                        onChange={this.passwordChange}
                        type="password"
                        autoComplete="current-password"
                        margin="normal"
                    />
                    <br />
                    <Button
                        className="sendButton"
                        variant="contained"
                        color="primary"
                        type="submit"
                        onClick={this.fetchLogin}>LOGIN
                    </Button>
                </ul>
            </div>
        )
    }
}

const mapStateToProps = state => ({
});

Login.propTypes = {
    value: PropTypes.string.isRequired
}

const mapDispatchToProps = {
    fetchLogin
};

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Login);

ログイン画面を表示するためのコンポーネントです。
render内部では
・TextField/email
・TextField/password
・Button/Login
の3つのパーツが用意されています。(気持ち程度のMaterial UI)

emailとpasswordについてはそれぞれonChangeで変更を検知してstateに反映する関数を用意しています。
LoginボタンのonClickで呼び出されるのはactions/index.jsのfetchLogin関数で、ReduxのActionが呼び出されることでAPIの結果がvalueに反映され、画面上の表示が更新されます。

emailとpasswordをReduxで管理しないのはログイン処理の後に持ち回る必要のない情報のためで、純粋にstateで管理しています。

mapDispatchToPropsに関しては上述したmapStateToPropsと同じように、ここで記述したDispatchをpropで利用することができます。


###actions/index.js

actions/index.js
export const REQUEST_LOGIN = 'REQUEST_LOGIN'

export const receiveLogin = (json) => ({
    type: REQUEST_LOGIN,
    value: json.name
})

export const fetchLogin = (email, password) => dispatch => {
    return fetch("http://略/login", {
        method: "POST",
        mode: "cors",
        headers: {
            "Content-Type": "application/json",
            "Accept": "application/json"
        },
        body: JSON.stringify({
            email: email,
            password: password
        })
    })
        .then(response => response.json())
        .then(function (json) {
            console.log(json);
            dispatch(receiveLogin(json));
        })
}

Login.jsのLoginボタンを押すと呼び出されるコンポーネントでReduxにおけるAction部分を担っています。
emailとpasswordをPOSTするとAPI側から{"name":"onigiri"}のようなデータが返却されます。

このデータをreceiveLoginで整形しdispatchを行うことでReducerが返却値を元にvalueを書き換えます。


###reducers/index.js

reducers/index.js
import { REQUEST_LOGIN } from '../actions'

const value = (state = { value: "" }, action) => {
    switch (action.type) {
        case REQUEST_LOGIN:
            return {
                ...state,
                value: action.value
            }
        default:
            return state
    }
}

const getLoginResult = (state = {}, action) => {
    switch (action.type) {
        case REQUEST_LOGIN:
            return {
                ...state,
                value: value(state.value, action)
            }
        default:
            return state
    }
}

export default getLoginResult

Reducerです。action/index.jsのfetchLoginでdispatchされるとgetLoginResultが呼び出され、valueの値を変更します。

##その他
Material UIを利用する上で、makeStylesを利用しようと思いましたがfunctionの中でしか呼び出せないらしく奮闘の末挫折しました。色々とfunction化したり頑張ったのですが一旦諦めさんです。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?