From 6fff9873dbfdbd072f2670913caa0955d012485a Mon Sep 17 00:00:00 2001 From: cefjoeii Date: Sun, 23 Jul 2017 01:08:06 +0800 Subject: [PATCH] Make the updating of data work --- README.md | 5 +- models/user.js | 6 +- react-src/src/components/App/App.css | 9 + react-src/src/components/App/App.js | 56 ++++-- .../FormAdd.js => FormUser/FormUser.js} | 58 ++++-- react-src/src/components/ModalAdd/ModalAdd.js | 39 ---- .../src/components/ModalUser/ModalUser.js | 30 ++++ .../TableData.js => TableUser/TableUser.js} | 33 ++-- routes/users.js | 170 ++++++++++++++---- 9 files changed, 284 insertions(+), 122 deletions(-) rename react-src/src/components/{FormAdd/FormAdd.js => FormUser/FormUser.js} (71%) delete mode 100644 react-src/src/components/ModalAdd/ModalAdd.js create mode 100644 react-src/src/components/ModalUser/ModalUser.js rename react-src/src/components/{TableData/TableData.js => TableUser/TableUser.js} (57%) diff --git a/README.md b/README.md index 8c9fe5b..50a1fc9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mern-crud -A CRUD starter kit using MongoDB, Express.js, React.js, and Node.js. Semantic UI React was used as I'm opinionatedly not pleased with Material-UI. +A CRUD starter kit using MongoDB, Express.js, React.js, and Node.js. Semantic UI React was used for the UI. Make sure MongoDB service is running. @@ -43,10 +43,11 @@ It will create a folder named *public* on the root directory. This is where the ## Features You Can Manually Add * Front-end validation. Pure back-end validation is expensive. +* Real-time broadcast using socket.io ## To Do - [x] Create - [x] Read -- [ ] Update +- [x] Update - [ ] Delete diff --git a/models/user.js b/models/user.js index 9ac18af..4b9829e 100644 --- a/models/user.js +++ b/models/user.js @@ -25,11 +25,7 @@ const emailValidator = [ ]; const ageValidator = [ - // validate({ - // validator: 'isInt', - // arguments: [1, 120], - // message: 'Age must be valid.' - // }) + // TODO: Make some validators here... ]; const genderValidator = [ diff --git a/react-src/src/components/App/App.css b/react-src/src/components/App/App.css index 262a950..26920cc 100644 --- a/react-src/src/components/App/App.css +++ b/react-src/src/components/App/App.css @@ -15,6 +15,15 @@ font-size: large; } +a.social-link { + color: #fff; + font-family: monospace; +} + +a.social-link:hover { + color: #ccc; +} + @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } diff --git a/react-src/src/components/App/App.js b/react-src/src/components/App/App.js index cb1757d..2d1b5ad 100644 --- a/react-src/src/components/App/App.js +++ b/react-src/src/components/App/App.js @@ -2,16 +2,16 @@ import React, { Component } from 'react'; import { Container } from 'semantic-ui-react'; import axios from 'axios'; -import TableData from '../TableData/TableData'; -import ModalAdd from '../ModalAdd/ModalAdd'; +import TableUser from '../TableUser/TableUser'; +import ModalUser from '../ModalUser/ModalUser'; import logo from '../../logo.svg'; import './App.css'; class App extends Component { - // Pass this as a prop - server = 'http://localhost:3000'; // http://localhost:3000 + // Change this when deploying. + server = 'http://localhost:3000'; constructor() { super(); @@ -20,9 +20,11 @@ class App extends Component { users: [] } - this.handleUsersListChange = this.handleUsersListChange.bind(this); + this.handleUserAdded = this.handleUserAdded.bind(this); + this.handleUserUpdated = this.handleUserUpdated.bind(this); } + // Fetch data from the back-end componentDidMount() { axios.get(`${this.server}/api/users/`) .then((response) => { @@ -33,12 +35,24 @@ class App extends Component { }); } - handleUsersListChange(addedUser) { - const users = this.state.users.slice(); - users.push(addedUser); - this.setState({ - users: users - }); + handleUserAdded(user) { + let users = this.state.users.slice(); + users.push(user); + this.setState({ users: users }); + } + + handleUserUpdated(user) { + let users = this.state.users.slice(); + for (let i = 0, n = users.length; i < n; i++) { + if (users[i]._id === user._id) { + users[i].name = user.name; + users[i].email = user.email; + users[i].age = user.age; + users[i].gender = user.gender; + break; //Stop this loop, we found it! + } + } + this.setState({ users: users }) } render() { @@ -47,14 +61,26 @@ class App extends Component {
logo -

MERN CRUD Starter Kit

+

MERN CRUD Starter Kit

A Create, Read, Update, and Delete starter kit using MongoDB, Express.js, React.js, and Node.js

-

Semantic UI React was used for the UI.

+

REST API was implemented on the back-end. Semantic UI React was used for the UI.

+

GitHubLinkedInTwitter

- - + +
diff --git a/react-src/src/components/FormAdd/FormAdd.js b/react-src/src/components/FormUser/FormUser.js similarity index 71% rename from react-src/src/components/FormAdd/FormAdd.js rename to react-src/src/components/FormUser/FormUser.js index 6f1119f..773a1c2 100644 --- a/react-src/src/components/FormAdd/FormAdd.js +++ b/react-src/src/components/FormUser/FormUser.js @@ -7,10 +7,11 @@ const genderOptions = [ { key: 'f', text: 'Female', value: 'f' }, ] -class FormAdd extends Component { +class FormUser extends Component { constructor(props) { super(props); + this.state = { name: '', email: '', @@ -26,6 +27,23 @@ class FormAdd extends Component { this.handleSubmit = this.handleSubmit.bind(this); } + componentWillMount() { + if (this.props.userID) { + axios.get(`${this.props.server}/api/users/?id=${this.props.userID}`) + .then((response) => { + this.setState({ + name: response.data.name, + email: response.data.email, + age: (response.data.age === null) ? '' : response.data.age, + gender: response.data.gender, + }); + }) + .catch((error) => { + console.log(error); + }); + } + } + handleInputChange(e) { const target = e.target; const value = target.type === 'checkbox' ? target.checked : target.value; @@ -44,26 +62,38 @@ class FormAdd extends Component { const newUser = { name: this.state.name, email: this.state.email, - age: parseInt(this.state.age, 10), + age: this.state.age, gender: this.state.gender } + const method = this.props.userID ? 'put' : 'post'; + const params = this.props.userID ? this.props.userID : ''; + axios({ - method: 'post', + method: method, responseType: 'json', - url: `${this.props.server}/api/users/`, + url: `${this.props.server}/api/users/${params}`, data: newUser }) .then((response) => { this.setState({ - name: '', - email: '', - age: '', - gender: '', formClassName: 'success', formSuccessMessage: response.data.msg }); - this.props.onUsersListChange(response.data.addedUser); + + if (!this.props.userID) { + this.setState({ + name: '', + email: '', + age: '', + gender: '' + }); + this.props.onUserAdded(response.data.result); + } + else { + this.props.onUserUpdated(response.data.result); + } + }) .catch((err) => { if (err.response) { @@ -77,7 +107,7 @@ class FormAdd extends Component { else { this.setState({ formClassName: 'warning', - formErrorMessage: 'Something went wrong.' + formErrorMessage: 'Something went wrong.' + err }); } }); @@ -110,10 +140,10 @@ class FormAdd extends Component { - +

{/* Yikes! */} ); } } -export default FormAdd; +export default FormUser; diff --git a/react-src/src/components/ModalAdd/ModalAdd.js b/react-src/src/components/ModalAdd/ModalAdd.js deleted file mode 100644 index e983c81..0000000 --- a/react-src/src/components/ModalAdd/ModalAdd.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { Component } from 'react'; -import { Button, Modal } from 'semantic-ui-react'; - -import FormAdd from '../FormAdd/FormAdd'; - -class ModalFormAdd extends Component { - - constructor(props) { - super(props); - - this.state = { - open: false - }; - } - - show = (size) => () => this.setState({ size, open: true }); - close = () => this.setState({ open: false }); - - render() { - const { open, size } = this.state; - - return ( -
- - - - - Add User - - - - - -
- ); - } -} - -export default ModalFormAdd; diff --git a/react-src/src/components/ModalUser/ModalUser.js b/react-src/src/components/ModalUser/ModalUser.js new file mode 100644 index 0000000..7fa83d9 --- /dev/null +++ b/react-src/src/components/ModalUser/ModalUser.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; +import { Button, Modal } from 'semantic-ui-react'; + +import FormUser from '../FormUser/FormUser'; + +class ModalUser extends Component { + + render() { + return ( + {this.props.buttonTriggerTitle}} + size='tiny' + > + {this.props.headerTitle} + + + + + ); + } +} + +export default ModalUser; diff --git a/react-src/src/components/TableData/TableData.js b/react-src/src/components/TableUser/TableUser.js similarity index 57% rename from react-src/src/components/TableData/TableData.js rename to react-src/src/components/TableUser/TableUser.js index 764291d..d92cea2 100644 --- a/react-src/src/components/TableData/TableData.js +++ b/react-src/src/components/TableUser/TableUser.js @@ -1,29 +1,36 @@ import React, { Component } from 'react'; import { Button, Table } from 'semantic-ui-react'; -const ButtonAction = () => ( -
- - -
-) +import ModalUser from '../ModalUser/ModalUser'; -class TableData extends Component { +class TableUser extends Component { render() { - const users = this.props.users; - let user = users.map((user) => + let users = this.props.users; + + users = users.map((user) => {user.name} {user.email} {user.age} {user.gender} - + + + + ); - user = [...user].reverse(); + users = [...users].reverse(); return ( @@ -37,11 +44,11 @@ class TableData extends Component { - {user} + {users}
); } } -export default TableData; +export default TableUser; diff --git a/routes/users.js b/routes/users.js index e8ea5ce..3083948 100644 --- a/routes/users.js +++ b/routes/users.js @@ -1,67 +1,169 @@ const express = require('express'); const router = express.Router(); +const mongoose = require('mongoose'); const config = require('../config/db'); const User = require('../models/user'); +sanitizeAge = (age) => { + if (isNaN(age) && age != '') return ''; + return (age === '') ? age : parseInt(age); +} + +// READ router.get('/', (req, res) => { - User.find({}).then((data) => { - res.json(data); + + // If query string is provided, send that specific user + if (req.query.id) { + User.findById(req.query.id) + .then((result) => { + res.json(result); + }) + .catch((err) => { + res.status(400).json({ success: false, msg: 'No such user.' }); + }); + return; // Skip the remaining code below + } + + // Send them all if no query string is provided + User.find({}) + .then((result) => { + res.json(result); }); + }); +// CREATE router.post('/', (req, res) => { + // Validate the age + let age = sanitizeAge(req.body.age); + if (age < 5 && age != '') return res.status(400).json({ success: false, msg: 'You\'re too young for this.'}); + else if (age > 130 && age != '') return res.status(400).json({ success: false, msg: 'You\'re too old for this.'}); + let newUser = new User({ name: req.body.name, email: req.body.email, - age: parseInt(req.body.age), + age: sanitizeAge(req.body.age), gender: req.body.gender }); - newUser.save((err, addedUser) => { + newUser.save() - if(err) { - if (err.errors) { + .then((result) => { + res.json({ + success: true, + msg: 'Successfully added!', + result: { + _id: result._id, + name: result.name, + email: result.email, + age: result.age, + gender: result.gender + } + }); + }) - if (err.errors.name) { - res.status(400).json({ success: false, msg: err.errors.name.message }); - return; - } + .catch((err) => { + if (err.errors) { + if (err.errors.name) { + res.status(400).json({ success: false, msg: err.errors.name.message }); + return; + } + if (err.errors.email) { + res.status(400).json({ success: false, msg: err.errors.email.message }); + return; + } + if (err.errors.age) { + res.status(400).json({ success: false, msg: err.errors.age.message }); + return; + } + if (err.errors.gender) { + res.status(400).json({ success: false, msg: err.errors.gender.message }); + return; + } + // Show failed if all else fails for some reasons + res.status(400).json( { success: false, msg: 'Something went wrong. Failed to add.'}); + } + }); +}); - if (err.errors.email) { - res.status(400).json({ success: false, msg: err.errors.email.message }); - return; - } +// UPDATE +router.put('/:id', (req, res) => { - if (err.errors.age) { - res.status(400).json({ success: false, msg: err.errors.age.message }); - return; - } + // Validate the age + let age = sanitizeAge(req.body.age); + if (age < 5 && age != '') return res.status(400).json({ success: false, msg: 'You\'re too young for this.'}); + else if (age > 130 && age != '') return res.status(400).json({ success: false, msg: 'You\'re too old for this.'}); - if (err.errors.gender) { - res.status(400).json({ success: false, msg: err.errors.gender.message }); - return; - } + let updatedUser = { + name: req.body.name, + email: req.body.email, + age: sanitizeAge(req.body.age), + gender: req.body.gender + }; - // Show failed if all else fails for some reasons - res.status(400).json( { success: false, msg: 'Failed to register.'}); - } - } + User.findOneAndUpdate({ _id: req.params.id }, updatedUser, { runValidators: true, context: 'query' }) + + .then((beforeResult) => { + + User.findOne({ _id: req.params.id }) - else { + .then((afterResult) => { res.json({ success: true, - msg: 'Successfully added!', - addedUser: { - _id: addedUser._id, - name: addedUser.name, - email: addedUser.email, - age: addedUser.age, - gender: addedUser.gender + msg: 'Successfully updated!', + result: { + _id: afterResult._id, + name: afterResult.name, + email: afterResult.email, + age: afterResult.age, + gender: afterResult.gender } }); + }) + + .catch((err) => { + res.status(400).json({ success: false, msg: 'Something went wrong. Failed to add.' }); + return; + }); + + }) + + .catch((err) => { + if (err.errors) { + if (err.errors.name) { + res.status(400).json({ success: false, msg: err.errors.name.message }); + return; + } + if (err.errors.email) { + res.status(400).json({ success: false, msg: err.errors.email.message }); + return; + } + if (err.errors.age) { + res.status(400).json({ success: false, msg: err.errors.age.message }); + return; + } + if (err.errors.gender) { + res.status(400).json({ success: false, msg: err.errors.gender.message }); + return; + } + // Show failed if all else fails for some reasons + res.status(400).json({ success: false, msg: 'Something went wrong. Failed to update.'}); } }); + +}); + +// DELETE +router.delete('/:id', (req, res) => { + // TODO: Coming soon... + User.findByIdAndRemove(req.params.id). + then((result) => { + res.json({ success: true, msg: 'It has been deleted.'}); + }) + .catch((err) => { + res.status(400).json({ success: false, msg: 'Nothing to delete.'}); + }); }); module.exports = router;