This repository has been archived by the owner on Feb 1, 2022. It is now read-only.
Sample application: dynamic client-side app using express-react-views #17
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
93cb833
dynamic react example generated using express react views
tenorviol ecbc23d
dynamic example: use jsx everywhere, instead of calling React.createF…
tenorviol f69dcfb
dynamic example: replaced Makefile with automagic reactify transform
tenorviol c5273bf
dynamic example: renamed all .jsx views with the .js extension
tenorviol 0a3125f
dynamic example: Using browserify standalone export instead of direct…
tenorviol a8780d6
dynamic example: fix for key warning
tenorviol 89d6d18
dynamic example: XSS explanation + .gitignore
tenorviol 0bf8ef4
dynamic example: moar commenting
tenorviol File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public/main.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
var express = require('express'); | ||
var reactViews = require('express-react-views'); | ||
|
||
var app = express(); | ||
|
||
app.set('view engine', 'js'); | ||
app.engine('js', reactViews.createEngine({ | ||
jsx: { | ||
extension: '.js' | ||
} | ||
})); | ||
|
||
app.use(express.static(__dirname + '/public')); | ||
|
||
app.get('/', function (req, res) { | ||
var initialState = { | ||
items: [ | ||
'document your code', | ||
'drop the kids off at the pool', | ||
'</script><script>alert(666)</script>' | ||
], | ||
text: '' | ||
}; | ||
res.render('Html', { data: initialState }); | ||
}); | ||
|
||
var port = process.env.PORT || 3000; | ||
app.listen(port, function () { | ||
console.log('Dynamic react example listening on port ' + port); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "dynamic-views", | ||
"version": "0.1.0", | ||
"description": "Example of creating a dynamic app using express-react-views", | ||
"author": "Chris Johnson <tenorviol@yahoo.com>", | ||
"private": true, | ||
"scripts": { | ||
"preinstall": "npm install ../../", | ||
"start": "browserify -t reactify --standalone main views/main.js -o public/main.js; node app.js" | ||
}, | ||
"dependencies": { | ||
"browserify": "^8.0.3", | ||
"express": "^4.10.6", | ||
"react": "^0.12.2", | ||
"react-tools": "^0.12.2", | ||
"reactify": "^0.17.1" | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
Dynamic react views example | ||
=========================== | ||
|
||
This example is the todo list borrowed from the | ||
[react.js main page](http://facebook.github.io/react/). | ||
We render the application server-side using express-react-views. | ||
An initial set of items has been added | ||
to illustrate populating data from the server. | ||
|
||
|
||
run it | ||
------ | ||
|
||
npm install | ||
npm start | ||
|
||
|
||
How it works | ||
------------ | ||
|
||
1. Separate the page into two templates, | ||
a [static container component](views/Html.jsx) | ||
and a [dynamic inner component](views/Content.jsx). | ||
|
||
2. Use express-react-views to render and serve the container. | ||
Server-side data can be sent via view options. | ||
|
||
3. Make your views available client-side as javascript. | ||
Here I created a [main](views/main.jsx) function for bootstrapping | ||
and packaged it up using [browserify](http://browserify.org/). | ||
|
||
4. Initialize the client-side app into the dynamic component | ||
using the same data from the server-side. | ||
This example passes the initial data to the client | ||
as the argument of the main function. | ||
Be mindful of potential XSS vulnerabilities. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
var React = require('react'); | ||
|
||
var TodoList = React.createClass({ | ||
render: function() { | ||
var i = 0; | ||
var createItem = function(itemText) { | ||
return <li key={i++}>{itemText}</li>; | ||
}; | ||
return <ul>{this.props.items.map(createItem)}</ul>; | ||
} | ||
}); | ||
|
||
var TodoApp = React.createClass({ | ||
getInitialState: function() { | ||
return this.props; | ||
}, | ||
onChange: function(e) { | ||
this.setState({text: e.target.value}); | ||
}, | ||
handleSubmit: function(e) { | ||
e.preventDefault(); | ||
var nextItems = this.state.items.concat([this.state.text]); | ||
var nextText = ''; | ||
this.setState({items: nextItems, text: nextText}); | ||
}, | ||
render: function() { | ||
return ( | ||
<div className="container"> | ||
<h3>TODO List</h3> | ||
<TodoList items={this.state.items} /> | ||
<form className="form-inline" onSubmit={this.handleSubmit}> | ||
<div className="form-group"> | ||
<input type="text" className="form-control" placeholder="Write task" onChange={this.onChange} value={this.state.text} /> | ||
| ||
</div> | ||
<button className="btn btn-primary">{'Add #' + (this.state.items.length + 1)}</button> | ||
</form> | ||
</div> | ||
); | ||
} | ||
}); | ||
|
||
module.exports = TodoApp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
var React = require('react'); | ||
var Content = require('./Content'); | ||
|
||
module.exports = React.createClass({ | ||
|
||
render: function() { | ||
var data = this.props.data; | ||
|
||
// render the content as a dynamic react component | ||
var contentHtml = React.renderToString(<Content {...data}/>); | ||
|
||
/** | ||
* re-render the content as json, | ||
* for client-side app initialization | ||
* | ||
* NOTE on XSS prevention: | ||
* | ||
* This text will be placed into a script tag. | ||
* It cannot be escaped, | ||
* because it is intended to be raw javascript. | ||
* Were the data object to contain the string, "</script>", | ||
* the script tag would terminate prematurely. | ||
* And two bad things would happen. | ||
* | ||
* 1. The client-side react application would not work. | ||
* 2. A second script tag could then run arbitrary javascript. | ||
* | ||
* The former sucks a little but the latter sucks a lot. | ||
* It would pwn you, game over, the site is no longer yours. | ||
* There are three ways to thwart this scenario and you should do all of them: | ||
* | ||
* 1. Scrub input from users. | ||
* Don't even let them enter data that is known to be potentially harmful. | ||
* 2. Use a templating library that renders text by default. | ||
* React does this, so YES! | ||
* 3. Whenever you have to write raw user content into the document, | ||
* block any content from breaking the current context. | ||
* | ||
* The third is what's going on with the `replace` function below. | ||
* Because we're in a script tag context, | ||
* we cannot allow the closing tag, "</script>", in our output. | ||
* This is an old trick that breaks up the word "script" into a string contatenation. | ||
* It works here because json always uses double quotes to escape strings. | ||
* | ||
* Properly escaping user data for raw output in html is tricky business. | ||
* Whenever possible, avoid it. | ||
* If avoidance is impossible, | ||
* know what you are doing and good luck. | ||
*/ | ||
var initScript = 'main(' + JSON.stringify(data).replace(/script/g, 'scr"+"ipt') + ')'; | ||
|
||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"/> | ||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/> | ||
</head> | ||
<body> | ||
<div id="content" dangerouslySetInnerHTML={{__html: contentHtml}}/> | ||
|
||
<script src="/main.js"></script> | ||
<script dangerouslySetInnerHTML={{__html: initScript}} /> | ||
|
||
</body> | ||
</html> | ||
); | ||
} | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
var React = require('react'); | ||
var Content = require('./Content'); | ||
|
||
module.exports = function (data, containerId) { | ||
var container = document.getElementById(containerId || 'content'); | ||
React.render( | ||
<Content {...data} />, | ||
container | ||
); | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This doesn't matter at all but you could also take advantage of the fact that
map
will call this with 3 args, the 2nd being the index :)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.
mind 💥
Sweet.