Skip to content

Commit fe62ce6

Browse files
committed
The validation error server-side now returns a custom Error through the promise, which the error handler middleware handles
1 parent a567747 commit fe62ce6

File tree

6 files changed

+48
-34
lines changed

6 files changed

+48
-34
lines changed

components/browser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var $ = require('jquery');
88
* This will avoid the server rendered comments being replaced with nothing by JS.
99
* If the AJAX call fails, then just render no comments after logging the error.
1010
*/
11-
window.renderCommentBox = function renderCommentBox() {
11+
window.renderCommentBox = function () {
1212
var url = "/api/comments";
1313

1414
$.get(url).then(

components/components.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
var React = require('react');
1515
var marked = require('marked');
16-
var classNames = require('classnames');
1716
var validation = require('react-validation-mixin');
1817
var $ = require('jquery');
1918

@@ -23,7 +22,7 @@ var schemas = require('./schemas');
2322

2423
var Comment = React.createClass({
2524
displayName: 'Comment',
26-
rawMarkup: function () {
25+
rawMarkup() {
2726
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
2827
return {__html: rawMarkup};
2928
},
@@ -33,7 +32,7 @@ var Comment = React.createClass({
3332

3433
var CommentBox = React.createClass({
3534
displayName: 'CommentBox',
36-
loadCommentsFromServer: function () {
35+
loadCommentsFromServer() {
3736
$.get(this.props.url).then(
3837
(data) => {
3938
this.setState({data: data});
@@ -43,7 +42,7 @@ var CommentBox = React.createClass({
4342
}
4443
);
4544
},
46-
handleCommentSubmit: function (comment) {
45+
handleCommentSubmit(comment) {
4746
var comments = this.state.data;
4847
// Optimistically set an id on the new comment. It will be replaced by an
4948
// id generated by the server. In a production application you would likely
@@ -66,28 +65,28 @@ var CommentBox = React.createClass({
6665
}.bind(this)
6766
});
6867
},
69-
getInitialState: function () {
68+
getInitialState() {
7069
return {data: this.props.comments};
7170
},
72-
componentDidMount: function () {
71+
componentDidMount() {
7372
this.loadCommentsFromServer();
7473
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
7574
},
76-
render: function () {
75+
render() {
7776
return templates.commentBox.apply(this, [CommentList, CommentForm]);
7877
}
7978
});
8079

8180
var CommentList = React.createClass({
8281
displayName: 'CommentList',
83-
render: function () {
82+
render() {
8483
return templates.commentList.apply(this, [Comment]);
8584
}
8685
});
8786

8887
var CommentForm = React.createClass({
8988
displayName: 'CommentForm',
90-
getInitialState: function () {
89+
getInitialState() {
9190
// Define the rules and custom messages for each field.
9291
this.validatorTypes = schemas.commentForm;
9392

@@ -98,7 +97,7 @@ var CommentForm = React.createClass({
9897
*
9998
* @param {Event} e
10099
*/
101-
activateValidation: function(e) {
100+
activateValidation(e) {
102101
strategy.activateRule(this.validatorTypes, e.target.name);
103102
this.props.handleValidation(e.target.name)(e);
104103
},
@@ -107,7 +106,7 @@ var CommentForm = React.createClass({
107106
*
108107
* @param {Event} e
109108
*/
110-
handleChange: function (e) {
109+
handleChange(e) {
111110
var state = {};
112111
state[e.target.name] = e.target.value;
113112

@@ -120,7 +119,7 @@ var CommentForm = React.createClass({
120119
*
121120
* @param {Event} e
122121
*/
123-
handleSubmit: function (e) {
122+
handleSubmit(e) {
124123
e.preventDefault();
125124

126125
this.props.validate((error) => {
@@ -131,13 +130,11 @@ var CommentForm = React.createClass({
131130
}
132131
});
133132
},
134-
getValidatorData: function () {
133+
getValidatorData() {
135134
return this.state;
136135
},
137-
getClasses: function (field) {
138-
return classNames({
139-
'has-error': !this.props.isValid(field)
140-
});
136+
getClassName(field) {
137+
return this.props.isValid(field) ? '' : 'has-error';
141138
},
142139
render: templates.commentForm
143140
});

components/strategy.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Validate using the validatorjs library as a strategy for react-validation-mixin
33
*
44
* @see https://www.npmjs.com/package/validatorjs
5-
* @see https://www.npmjs.com/package/react-validator-mixin
5+
* @see https://jurassix.gitbooks.io/docs-react-validation-mixin/content/overview/strategies.html
66
*/
77

88
'use strict';
@@ -130,9 +130,19 @@ module.exports = {
130130
resolve();
131131
},
132132
() => {
133-
reject(validator.errors.all());
133+
var e = new this.Error('A validation error occurred');
134+
e.errors = validator.errors.all();
135+
136+
reject(e);
134137
}
135138
);
136139
});
137-
}
140+
},
141+
/**
142+
* Extension of the built-in Error. Created by validateServer when validation fails.
143+
* Exists so that middleware can check it with instanceof: if (err instanceof strategy.Error)
144+
*
145+
* @property {Object} errors Contains the error messages by field name.
146+
*/
147+
Error: class extends Error {}
138148
};

components/templates.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
var React = require('react');
22

33
module.exports = {
4-
comment: function () {
4+
comment() {
55
return (
66
<div className="comment">
77
<h2 className="commentAuthor">
@@ -11,7 +11,7 @@ module.exports = {
1111
</div>
1212
);
1313
},
14-
commentBox: function (CommentList, CommentForm) {
14+
commentBox(CommentList, CommentForm) {
1515
return (
1616
<div className="commentBox">
1717
<h1>Comments</h1>
@@ -20,7 +20,7 @@ module.exports = {
2020
</div>
2121
);
2222
},
23-
commentList: function (Comment) {
23+
commentList(Comment) {
2424
var commentNodes = this.props.data.map(function(comment) {
2525
return (
2626
<Comment author={comment.author} key={comment.id}>
@@ -35,7 +35,7 @@ module.exports = {
3535
</div>
3636
);
3737
},
38-
commentForm: function () {
38+
commentForm() {
3939
function renderErrors(messages) {
4040
if (messages.length) {
4141
messages = messages.map((message) => <li>{message}</li>);
@@ -51,7 +51,7 @@ module.exports = {
5151
type="text"
5252
placeholder="Your name"
5353
name="author"
54-
className={this.getClasses('author')}
54+
className={this.getClassName('author')}
5555
value={this.state.author}
5656
onChange={this.handleChange}
5757
onBlur={this.activateValidation}
@@ -65,7 +65,7 @@ module.exports = {
6565
type="text"
6666
placeholder="Say something..."
6767
name="text"
68-
className={this.getClasses('text')}
68+
className={this.getClassName('text')}
6969
value={this.state.text}
7070
onChange={this.handleChange}
7171
onBlur={this.activateValidation}

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"main": "server.js",
66
"dependencies": {
77
"body-parser": "^1.4.3",
8-
"classnames": "^2.2.3",
98
"express": "^4.4.5",
109
"install": "^0.4.2",
1110
"jquery": "^2.2.0",

server.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ app.use(express.static(path.join(__dirname, 'public')));
3737
app.use(bodyParser.json());
3838
app.use(bodyParser.urlencoded({extended: true}));
3939

40-
4140
app.get('/', function(req, res) {
4241
fs.readFile(COMMENTS_FILE, function(err, data) {
4342
if (err) {
@@ -74,7 +73,8 @@ app.get('/api/comments', function(req, res) {
7473
});
7574
});
7675

77-
app.post('/api/comments', function(req, res) {
76+
app.post('/api/comments', function(req, res, next) {
77+
// Validate the request and if it fails, call the error handler
7878
strategy.validateServer(req.body, schemas.commentForm).then(() => {
7979
fs.readFile(COMMENTS_FILE, function(err, data) {
8080
if (err) {
@@ -100,12 +100,20 @@ app.post('/api/comments', function(req, res) {
100100
});
101101
});
102102
})
103-
.catch((errors) => {
104-
// Handle validation errors
105-
res.status(400).json(errors);
106-
});
103+
.catch(next);
107104
});
108105

106+
/**
107+
* If a validation error, output a 400 JSON response containing the error messages.
108+
* Otherwise, use the default error handler.
109+
*/
110+
app.use(function(err, req, res, next) {
111+
if (err instanceof strategy.Error) {
112+
res.status(400).json(err.errors);
113+
} else {
114+
next(err, req, res);
115+
}
116+
});
109117

110118
app.listen(app.get('port'), function() {
111119
console.log('Server started: http://localhost:' + app.get('port') + '/');

0 commit comments

Comments
 (0)