#概要
シンプルな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
を以下のように書き換えます.
{
"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はこのような感じになります.
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は以下のような感じになります.
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を作ります.
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.js
とList.js
がその子コンポーネントという構成になっています.
なお, 今回はmapDispatchToProps
とmapStateToProps
を使いたかったので非常に回りくどい書き方をしております.
シンプルに書きたい方は上で紹介した記事をご参照ください.
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コンポーネントです.
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コンポーネントです.
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
#Happy Hacking !