React + Reduxでメモアプリを作成(完成版)

NO IMAGE

メモアプリ完成版ということで、
全部のコードを載せておきたいと思います。

動かない場合や参考になれば幸いです。

*ディレクトリ「src」以下のファイルの編集になります。

ファイル名

action/index.js

// アクション定義
export const READ_MEMOS = "READ_MEMOS";
export const READ_MEMO = "READ_MEMO";
export const ADD_MEMO = "ADD_MEMO";
export const UPDATE_MEMO = "UPDATE_MEMO";
export const DELETE_MEMO = 'DELETE_MEMO';

// 一覧取得
export const getMemos = () => ({
	type: READ_MEMOS,
});

// 一件取得
export const getMemo = (id) => ({
	type : READ_MEMO,
	params : id,
})

// メモの新規作成
export const addMemo = (values) => ({
	type : ADD_MEMO,
	params : values,
});

// 更新
export const updateMemo = (id, values) => ({
	type : UPDATE_MEMO,
	id : id,
	params : values,
});

// 削除
export const deleteMemo = (id) => ({
	type : DELETE_MEMO,
	id : id,
});

reducers/index.js

import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import memos from './memo';

export default combineReducers({ memos, form });

reducers/memo.js

import { v4 as uuidv4 } from 'uuid';
import {
	READ_MEMOS,
	READ_MEMO,
	ADD_MEMO,
	UPDATE_MEMO,
	DELETE_MEMO,
} from '../actions';

export default (memos = {}, action) => {
	switch (action.type) {
		case READ_MEMOS:
			return memos;
		case READ_MEMO:
			return memos;
		case ADD_MEMO:
			var uid = uuidv4();
			const insertData = {
				id : uid,
				title : action.params.title,
				memo : action.params.memo,
			}
			return {...memos, [uid] : insertData};
		case UPDATE_MEMO:
			const updateData = {
				id : action.id,
				title : action.params.title,
				memo : action.params.memo,
			}
			return {...memos, [updateData.id] : updateData};
		case DELETE_MEMO:
			delete memos[action.id];
			return {...memos};
		default:
			return memos;
	}
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
// import { createStore } from 'redux';
import { Switch, BrowserRouter, Route } from 'react-router-dom';

import { PersistGate } from 'redux-persist/integration/react';
import store, { persistor } from './configStore';

import reducer from './reducers';
import * as serviceWorker from './serviceWorker';

import MemoIndex from './components/memoIndex';
import MemoNew from './components/memoNew';
import MemoShow from './components/memoShow';

// const store = createStore(reducer);

ReactDOM.render(
	<Provider store={store}>
		<PersistGate loading={null} persistor={persistor}>
			<BrowserRouter>
				<Switch>
					<Route path="/new" component={MemoNew} />
					<Route path="/show/:id" component={MemoShow} />
					<Route exact path="/" component={MemoIndex}/>
				</Switch>
			</BrowserRouter>
		</PersistGate>
	</Provider>,
	document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

components/memoIndex.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';

import { getMemos } from '../actions';
import _ from 'lodash';

class MemoIndex extends Component {
	
	componentDidMount() {
		this.props.getMemos();
	}
	
	renderMemos = () => {
		const memos = this.props.memos;
		return _.map(memos, memo =>(
			<tr key={memo.id}>
				<td>
					<Link to={`/show/${memo.id}`}>
						{memo.title}
					</Link>
				</td>
				<td>{memo.memo}</td>
			</tr>
		));
	}
	
	render() {
		return (
			<React.Fragment>
				<table>
					<thead>
						<tr>
							<th>タイトル</th>
							<th>メモ</th>
						</tr>
					</thead>
					<tbody>
						{this.renderMemos()}
					</tbody>
				</table>
				<Link to="/new">追加</Link>
			</React.Fragment>
		);
	}
}

const mapStateToProps = state => ({ memos : state.memos });

const mamDispatchToProps = ({ getMemos });

export default connect(mapStateToProps, mamDispatchToProps)(MemoIndex);

conponents/memoShow.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Link } from 'react-router-dom';

import { getMemo, updateMemo, deleteMemo } from '../actions';

class memoShow extends Component {
	
	componentDidMount() {
		const {id} = this.props.match.params;
		if (id) this.props.getMemo(id);
	}
	
	renderField = (field) => {
		const {input, label, type, meta : {touched, error}} = field;
		return (
			<div>
				<input {...input} placeholder={label} type={type} />
				{touched && error && <span>{error}</span>}
			</div>
		);
	}
	
	onSubmit = (values) => {
		const {id} = this.props.match.params;
		this.props.updateMemo(id, values);
		this.props.history.push('/');
	}
	
	onDeleteClick = () => {
		const { id } = this.props.match.params;
		this.props.deleteMemo(id);
		this.props.history.push('/');
	}
	
	render() {
		const {handleSubmit, pristine, submitting, invalid} = this.props;
		console.log(submitting);
		return (
			<form onSubmit={handleSubmit(this.onSubmit)}>
				<div><Field label="タイトル" name="title" type="text" component={this.renderField} /></div>
				<div><Field label="メモ" name="memo" type="text" component={this.renderField} /></div>
				<div>
					<div><input type="submit" value="更新" disabled={pristine || submitting || invalid} /></div>
					<div><Link to="/">キャンセル</Link></div>
					<div><Link to="/" onClick={this.onDeleteClick}>削除</Link></div>
				</div>
			</form>
		);
	}
}

const validate = values => {
	const errors = {};
	if (!values.title) errors.title = 'タイトルが入力されていません。';
	if (!values.memo) errors.memo = 'メモを入力してください。';
	return errors;
}

const mapStateToProps = (state, ownProps) => {
	const memo = state.memos[ownProps.match.params.id];
	return { initialValues : memo }
};

const mapDispatchToProps = ({ getMemo, updateMemo, deleteMemo });

export default connect(mapStateToProps, mapDispatchToProps)(
	reduxForm({validate, form: 'memoShowForm', enableReinitialize: true})(memoShow)
);

compopnents/memoNew.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Link } from 'react-router-dom';

import { addMemo } from '../actions';

class memoNew extends Component {
	renderField = (field) => {
		const {input, label, type, meta:{touched, error} } = field;
		return (
			<div>
				<input {...input} placeholder={label} type={type} />
				{touched && error && <span>{error}</span>}
			</div>
		);
	}
	
	onSubmit = (values) => {
		this.props.addMemo(values);
		this.props.history.push('/');
	}

	render() {
		const { handleSubmit, pristine, submitting, invalid } = this.props;
		return (
			<form onSubmit={handleSubmit(this.onSubmit)}>
				<div>
					<Field label="タイトル" name="title" type="text" component={this.renderField} />
				</div>
				<div>
					<Field label="メモ" name="memo" type="text" component={this.renderField} />
				</div>
				<div>
					<div><input type="submit" value="追加" disabled={pristine || submitting || invalid } /></div>
					<div><Link to="/">キャンセル</Link></div>
				</div>
			</form>
		);
	}
}

const validate = values => {
	const errors = {};
	if (!values.title) errors.title = 'タイトルが入力されていません。';
	if (!values.memo) errors.memo = 'メモを入力してください。';
	return errors;
}

const mapDispatchToProps = ({ addMemo });

export default connect(null, mapDispatchToProps)(
	reduxForm({validate, form: 'memoNewForm'})(memoNew)
);

configStore.js

import { createStore } from 'redux';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

import rootReducer from './reducers';

const persistConfig = {
	key: 'root',
	storage,
	whitelist: ['memos']
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = createStore(
	persistedReducer,
);

export const persistor = persistStore(store);
export default store;

JavaScriptカテゴリの最新記事