-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Universal React forms #8
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import React from 'react'; | ||
import { Link } from 'react-router'; | ||
import validateEmailForm from './validate-email-form'; | ||
|
||
export default class EmailForm extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
const { success, errors, value } = props; | ||
this.state = { | ||
success, | ||
errors, | ||
value: value || '' | ||
}; | ||
this.formSubmit = this.formSubmit.bind(this); | ||
this.inputChange = this.inputChange.bind(this); | ||
this.resetForm = this.resetForm.bind(this); | ||
} | ||
|
||
formSubmittedSuccessfully() { | ||
return this.state.success === true; | ||
} | ||
|
||
inputChange(e) { | ||
this.setState({ | ||
value: e.target.value | ||
}); | ||
} | ||
|
||
resetForm(e) { | ||
e.preventDefault(); | ||
this.setState({ | ||
success: undefined, | ||
errors: [], | ||
value: '' | ||
}); | ||
} | ||
|
||
formSubmit(e) { | ||
e.preventDefault(); | ||
const emailValue = this.state.value; | ||
// note how this is the same validator we ran on the server! | ||
const result = validateEmailForm(emailValue); | ||
// at this point you could POST to an endpoint to save | ||
// this to the database, or do whatever you need to | ||
this.setState({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a real app you'd probably do something here to store this piece of data - most likely hitting an API endpoint. The API endpoint wouldn't do any validation though, just store into the database, because the validation has been done on the client. Alternatively you could just do the validation all on the server and return all the validation input, but why do that when we can have it all on the client? :) |
||
errors: result.errors, | ||
success: result.valid | ||
}); | ||
} | ||
|
||
renderErrorsList() { | ||
return this.state.errors.map((err) => { | ||
return ( | ||
<li className="form-error" key={err}> | ||
<p>{err}</p> | ||
</li> | ||
); | ||
}); | ||
} | ||
|
||
renderForm() { | ||
return ( | ||
<form method="post" action="/submit-email-form" onSubmit={this.formSubmit}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a non-JS world the form will submit like a regular form would to |
||
|
||
<div className="field"> | ||
<label>Your email address</label> | ||
<input | ||
onChange={this.inputChange} | ||
type="text" | ||
value={this.state.value} | ||
name="email" | ||
placeholder="[email protected]" | ||
ref="emailInput" /> | ||
</div> | ||
<button type="submit">Send</button> | ||
</form> | ||
); | ||
} | ||
|
||
renderSuccess() { | ||
return ( | ||
<div> | ||
<p>Thanks, {this.state.value} has been added to our totally cool list.</p> | ||
<a href="/about" onClick={this.resetForm}>Reset page</a> | ||
</div> | ||
) | ||
} | ||
|
||
renderErrors() { | ||
if (this.state.errors && this.state.errors.length > 0) { | ||
return ( | ||
<div> | ||
<h5>There were errors submitting this form.</h5> | ||
<ul>{this.renderErrorsList()}</ul> | ||
</div> | ||
); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
render() { | ||
return ( | ||
<div> | ||
{ this.renderErrors() } | ||
{ this.formSubmittedSuccessfully() ? this.renderSuccess() : this.renderForm() } | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
EmailForm.propTypes = { | ||
success: React.PropTypes.bool, | ||
errors: React.PropTypes.arrayOf(React.PropTypes.string) | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// write any logic for your form in one file that can be used on both the client and the server | ||
|
||
export default function(email) { | ||
// IRL you probably want a better check! | ||
if (email.indexOf('@') > -1) { | ||
return { | ||
valid: true, | ||
email, | ||
errors: [] | ||
} | ||
} else { | ||
return { | ||
valid: false, | ||
email, | ||
errors: [ | ||
`Email address ${email} is not valid` | ||
] | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the magic bit - all of your form's logic should be in a file that can be webpacked on the client or used on the server. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (A real app would have a much more complex version of this, I get that!). Also doing it like this makes it really easy to test. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,17 @@ import express from 'express'; | |
import React from 'react'; | ||
import { renderToString } from 'react-dom/server'; | ||
import { match, RouterContext } from 'react-router'; | ||
import bodyParser from 'body-parser'; | ||
|
||
import { routes } from './routes'; | ||
|
||
import validateEmailForm from './components/validate-email-form'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. omg the same validator we used on the client! |
||
|
||
const app = express(); | ||
|
||
app.use(express.static('public')); | ||
app.use(bodyParser.json()); | ||
app.use(bodyParser.urlencoded({ extended: true })); | ||
|
||
app.set('view engine', 'ejs'); | ||
|
||
|
@@ -36,7 +41,22 @@ app.get('*', (req, res) => { | |
} | ||
}); | ||
}); | ||
app.listen(3003, 'localhost', function(err) { | ||
|
||
app.post('/submit-email-form', (req, res) => { | ||
const { email } = req.body; | ||
const result = validateEmailForm(email); | ||
|
||
if (result.valid === true) { | ||
// here you would save this to a database, or whatever you want | ||
// db.addEmail(email) | ||
|
||
res.redirect(`/about?success=true&value=${email}`); | ||
} else { | ||
res.redirect(`/about?errors=${result.errors.join(',')}&success=false&value=${email}`); | ||
} | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the route that the form will POST to if there's no JS. Notice how it's tiny - the logic is encapsulated in |
||
|
||
app.listen(3003, 'localhost', (err) => { | ||
if (err) { | ||
console.log(err); | ||
return; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the form is submitted on the server, the URL query params contain info about the result:
/about?succes=true&[email protected]
, and this code tidies them and passes them down toEmailForm