LoginSignup
32

More than 3 years have passed since last update.

nodejs + mysql + React + ReduxでCRUDアプリを作る Part2

Posted at

概要

シンプルなCRUD(create, read, update, delete)アプリをデータベースはmysql, フロントエンドはReact + Reduxで作ってみます.
今回はReact + Reduxでフロントエンドを作ります.
Part1はこちら
なお、今回はこちらの記事をほぼパクらせていただきました. ありがとうございます.

セットアップ

まずcreate-react-appで雛形を作ります.

$ cd crud-node
$ npx create-react-app client
$ cd client

次にpackage.jsonを以下のように書き換えます.

package.json
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.19.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.0.3",
    "react-scripts": "3.0.1",
    "redux": "^4.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

で、npm installしましょう

$ npm install

reducer

reducerはこのような感じになります.

client/src/reducers/index.js
import { combineReducers } from 'redux'
import {
    CHANGE_NAME, CHANGE_STATUS, INITIALIZE_FORM,
    REQUEST_DATA, RECEIVE_DATA_SUCCESS, RECEIVE_DATA_FAILED
} from '../actions';

const initialState = {
    form: {
        name: '',
        status: '',
        ustatus: ''
    },
    users: {
        isFetching: false,
        users: []
    }
}

const formReducer = (state = initialState.form, action) => {
    switch(action.type) {
        case CHANGE_NAME:
            return {
                ...state,
                name: action.name
            }
        case CHANGE_STATUS: 
            return {
                ...state,
                status: action.status
            }
        case INITIALIZE_FORM:
            return initialState.form
        default:
            return state
    }
}

const usersReducer = (state = initialState.users, action) => {
    switch(action.type) {
        case REQUEST_DATA:
            return {
                ...state,
                isFetching: true
            }
        case RECEIVE_DATA_SUCCESS:
            return {
                ...state,
                isFetching: false,
                users: action.users
            }
        case RECEIVE_DATA_FAILED:
            return {
                ...state,
                isFetching: false
            }
        default: 
            return state;
    }
}

const rootReducer = combineReducers({
    form: formReducer,
    users: usersReducer
})

export default rootReducer;

入力フォーム用のformReducerとデータベースの読み取りの時用のusersReducerを作って, rootReducerでまとめています.

action

actionは以下のような感じになります.

client/src/actions/index.js
export const CHANGE_NAME = 'CHANGE_NAME';
export const CHANGE_STATUS = 'CHANGE_STATUS';
export const CHANGE_USTATUS = 'CHANGE_USTATUS';
export const INITIALIZE_FORM = 'INITIALIZE_FORM';
export const REQUEST_DATA = 'REQUEST_DATA';
export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS';
export const RECEIVE_DATA_FAILED = 'RECEIVE_DATA_FAILED';

export const changeName = (name) => ({
    type: CHANGE_NAME,
    name
})

export const changeStatus = (status) => ({
    type: CHANGE_STATUS,
    status
})

export const initializeForm = () => ({
    type: INITIALIZE_FORM
})

export const requestData = () => ({
    type: REQUEST_DATA
})

export const receiveDataSuccess = (users) => ({
    type: RECEIVE_DATA_SUCCESS,
    users
})

export const receiveDataFailed = () => ({
    type: RECEIVE_DATA_FAILED
})

changeUstatus はstatus更新のための入力フォーム用です.

store

storeを作ります.

client/src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import App from './components/App'
import rootReducer from './reducers'
import { Provider } from 'react-redux'

const store = createStore(rootReducer);

console.log(store);

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

component

componentは全部で3つでApp.jsが親コンポーネント, Form.jsList.jsがその子コンポーネントという構成になっています.
なお, 今回はmapDispatchToPropsmapStateToPropsを使いたかったので非常に回りくどい書き方をしております.
シンプルに書きたい方は上で紹介した記事をご参照ください.

client/src/compoents/App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed } from '../actions';
import Form from './Form';
import List from './List';

class App extends Component {
    render() {
        return (
            <div>
              <Form 
                changeName={this.props.changeName} 
                changeStatus={this.props.changeStatus} 
                initializeForm={this.props.initializeForm}
                requestData={this.props.requestData}
                receiveDataSuccess={this.props.receiveDataSuccess}
                receiveDataFailed={this.props.receiveDataFailed}
                name={this.props.name}
                status={this.props.status}
                />
              <List
                initializeForm={this.props.initializeForm}
                requestData={this.props.requestData}
                receiveDataSuccess={this.props.receiveDataSuccess}
                receiveDataFailed={this.props.receiveDataFailed}
                isFetching={this.props.isFetching}
                ustatus={this.props.ustatus}
                users={this.props.users}
                />
            </div>
        )
    }
} 

const mapDispatchToProps = ({ changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed });

const mapStateToProps = state => ({ name: state.form.name, status: state.form.status,  users: state.users.users, isFetching: state.users.isFetching });

export default connect(mapStateToProps,mapDispatchToProps)(App)

以下はユーザーを登録するためのFormコンポーネントです.

client/src/compoents/Form.js
import React from 'react';
import axios from 'axios';

const ROOT_ENDPOINT = 'http://localhost:3001';

const Form = ({ name, status, changeName, changeStatus, initializeForm, requestData, receiveDataSuccess, receiveDataFailed }) => {
   const createUser = e => {
       if(name.length > 10 || status.length > 10) {
           alert('文字数が多いです');
       } else {
           e.preventDefault();
           axios({
             method: 'post',
             url: ROOT_ENDPOINT + '/user/create',
             data: {
                name: name,
                status: status
             }
           })
           .then(res => {
             initializeForm();
             const _users = res.data;
             console.log(_users);
             receiveDataSuccess(_users);
           })
           .catch(err => {
             console.log(err);
             alert('登録に失敗しました')
             receiveDataFailed();
           })
       }
    }

    return (
        <div>
            <form onSubmit={e => createUser(e)}>
                <label>
                    name:
                    <input value={name} onChange={e => changeName(e.target.value)} />
                </label>
                <label>
                    status:
                    <input value={status} onChange={e => changeStatus(e.target.value)} />
                </label>
                <button type="submit">register</button>
            </form>
        </div>
    )
}

export default Form;

以下はユーザーを表示, 更新, 削除するためのListコンポーネントです.

client/src/compoents/List.js
import React from 'react';
import axios from 'axios';

const ROOT_ENDPOINT = 'http://localhost:3001';


class List extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount = () => {
        this.fetchData();
    }

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

    fetchData = () => {
        this.props.requestData();
        axios.get(ROOT_ENDPOINT + '/user')
        .then(res => {
            const _users = res.data;
            this.props.receiveDataSuccess(_users);
        })
        .catch(err => {
            console.log(err);
            this.props.receiveDataFailed();
        })
    }

    updateUser = (id) => {
        if(this.state[`${id}`].length > 10) {
            alert('文字数が多いです');
        } else {
            this.props.requestData();
            axios({
              method: 'put',
              url: ROOT_ENDPOINT + '/user/update',
              data: {
                id: id,
                status: this.state[`${id}`]
              }
            })
            .then(res => {
              const _users = res.data;
              this.props.receiveDataSuccess(_users);
            })
            .catch(err => {
              console.log(err);
              alert('更新に失敗しました');
              this.props.receiveDataFailed();
            })
        }
    }

    deleteUser = (id) => {
        this.props.requestData();
        axios({
            method: 'delete',
            url: ROOT_ENDPOINT + '/user/delete',
            data: {
                id: id
            }
        })
        .then(res => {
            const _users = res.data;
            this.props.receiveDataSuccess(_users);
        })
        .catch(err => {
            console.log(err);
            alert('削除に失敗しました');
            this.props.receiveDataFailed();
        })
    }

    render() {
        return (
            <div>
                {
                    this.props.isFetching
                        ? <h2>Now Loading...</h2>
                        : <div>
                            <ul>
                                {this.props.users.map(user => (
                                    <li key={user.id}>
                                        {`${user.name}: ${user.status}`}
                                        <input  onChange={this.handleChange(`${user.id}`)} />
                                        <button onClick={() => this.updateUser(user.id)}>update</button>
                                        <button onClick={() => this.deleteUser(user.id)}>delete</button>
                                    </li>
                                ))}
                            </ul>
                          </div>
                }
            </div>
        )
    }
}

export default List;

ソースコードはこちらです
https://github.com/melonattacker/crud-app

デモ

out.gif

Happy Hacking :sunglasses: !

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
32