Tutorial React
Tutorial React
JS FOR BEGINER
This tutorial covers the end-to-end process of creating a brand new ASP.NET MVC website and
adding a React component in it. We will start from scratch and end with a fully functioning
component. It assumes you have basic knowledge of ASP.NET MVC and using Visual Studio.
This tutorial is based off the original React tutorial but has been modified specifically for
ReactJS.NET.
We'll be building a simple, but realistic comments box that you can drop into a blog, a basic
version of the realtime comments offered by Disqus, LiveFyre or Facebook comments.
We'll provide:
Optimistic commenting: comments appear in the list before they're saved on the server
so it feels fast.
Live updates: as other users comment we'll pop them into the comment view in real time
Markdown formatting: users can use Markdown to format their text
Getting started
For this tutorial we'll be using Visual Studio 2013, although any version of Visual Studio from
2010 onwards is fine, including Visual Studio Express 2013 which is completely free. We will
be using ASP.NET MVC 4, although similar steps apply for ASP.NET MVC 5.
New Project
Start by creating a new ASP.NET MVC 4 project:
1. File New Project
2. Select ".NET Framework 4" and Templates Visual C# Web ASP.NET MVC 4
Web Application. Call it "ReactDemo"
3. In the "New ASP.NET MVC 4 Project" dialog, select the Empty template. I always
recommend using this template for new sites, as the others include a large amount of
Install ReactJS.NET
We need to install ReactJS.NET to the newly-created project. This is accomplished using NuGet,
a package manager for .NET. Right-click on the "ReactDemo" project in the Solution Explorer
and select "Manage NuGet Packages". Search for "ReactJS.NET" and install the ReactJS.NET
(MVC 4 and 5) package.
Note: In a real ASP.NET MVC site, you'd use a layout. However, to keep this tutorial simple, we
will keep all HTML in the one view file.
Replace the contents of the new view file with the following:
@{
Layout = null;
}
<html>
<head>
<title>Hello React</title>
</head>
<body>
<div id="content"></div>
<script src="https://fb.me/react-15.0.1.js"></script>
<script src="https://fb.me/react-dom-15.0.1.js"></script>
<script src="@Url.Content("~/Scripts/Tutorial.jsx")"></script>
</body>
</html>
Let's build the CommentBox component, which is just a simple <div>. Add this code to
Tutorial.jsx:
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
ReactDOM.render(
<CommentBox />,
document.getElementById('content')
);
At this point, run your application by clicking the "Play" button in Visual Studio. If successful,
your default browser should start and you should see "Hello, world! I am a CommentBox."
If you see this, congratulations! You've just built your first React component. You can leave the
application running while you continue this tutorial. Simply change the JSX file and refresh to
see your changes.
JSX Syntax
The first thing you'll notice is the XML-ish syntax in your JavaScript. We have a simple
precompiler that translates the syntactic sugar to this plain JavaScript:
var CommentBox = React.createClass({displayName: 'CommentBox',
render: function() {
return (
React.createElement('div', {className: "commentBox"},
"Hello, world! I am a CommentBox."
)
);
}
});
ReactDOM.render(
React.createElement(CommentBox, null),
document.getElementById('content')
);
Its use is optional but we've found JSX syntax easier to use than plain JavaScript. Read more on
the JSX Syntax article.
What's going on
We pass some methods in a JavaScript object to React.createClass() to create a new React
component. The most important of these methods is called render which returns a tree of React
components that will eventually render to HTML.
The <div> tags are not actual DOM nodes; they are instantiations of React div components. You
can think of these as markers or pieces of data that React knows how to handle. React is safe.
We are not generating HTML strings so XSS protection is the default.
You do not have to return basic HTML. You can return a tree of components that you (or
someone else) built. This is what makes React composable: a key tenet of maintainable
frontends.
ReactDOM.render()
instantiates the root component, starts the framework, and injects the
markup into a raw DOM element, provided as the second argument.
Composing components
Let's build skeletons for CommentList and CommentForm which will, again, be simple <div>s:
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
Hello, world! I am a CommentList.
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
Notice how we're mixing HTML tags and components we've built. HTML components are
regular React components, just like the ones you define, with one difference. The JSX compiler
Component Properties
Let's create our third component, Comment. We will want to pass it the author name and
comment text so we can reuse the same code for each unique comment. First let's add some
comments to the CommentList:
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
<Comment author="Daniel Lo Nigro">Hello ReactJS.NET World!</Comment>
<Comment author="Pete Hunt">This is one comment</Comment>
<Comment author="Jordan Walke">This is *another* comment</Comment>
</div>
);
}
});
Note that we have passed some data from the parent CommentList component to the child
Comment components. For example, we passed Pete Hunt (via an attribute) and This is one
comment (via an XML-like child node) to the first Comment. Data passed from parent to children
components is called props, short for properties.
Using props
Let's create the Comment component. Using props we will be able to read the data passed to it
from the CommentList, and render some markup:
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
By surrounding a JavaScript expression in braces inside JSX (as either an attribute or child), you
can drop text or React components into the tree. We access named attributes passed to the
component as keys on this.props and any nested elements as this.props.children.
Adding Markdown
Markdown is a simple way to format your text inline. For example, surrounding text with
asterisks will make it emphasized.
First, add the third-party Showdown library to your application. This is a JavaScript library
which takes Markdown text and converts it to raw HTML. We will add it via NuGet (search for
"Showdown" and install it, similar to how you installed ReactJS.NET earlier) and reference the
script tag in your view:
<script
<script
<script
<script
src="https://fb.me/react-15.0.1.js"></script>
src="https://fb.me/react-dom-15.0.1.js"></script>
src="@Url.Content("~/Scripts/showdown.min.js")"></script>
src="@Url.Content("~/Scripts/Tutorial.jsx")"></script>
Next, let's convert the comment text to Markdown and output it:
var Comment = React.createClass({
render: function() {
var converter = new Showdown.converter();
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{converter.makeHtml(this.props.children.toString())}
</div>
);
}
});
All we're doing here is calling the Showdown library. We need to convert
this.props.children from React's wrapped text to a raw string that Showdown will
understand so we explicitly call toString().
But there's a problem! Our rendered comments look like this in the browser: "<p>This is
<em>another</em> comment</p>". We want those tags to actually render as HTML.
That's React protecting you from an XSS attack. There's a way to get around it but the
framework warns you not to use it:
var Comment = React.createClass({
render: function() {
var converter = new Showdown.converter();
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
This is a special API that intentionally makes it difficult to insert raw HTML, but for Showdown
we'll take advantage of this backdoor.
Remember: by using this feature you're relying on Showdown to be secure.
data = [
Author: "Daniel Lo Nigro", Text: "Hello ReactJS.NET World!" },
Author: "Pete Hunt", Text: "This is one comment" },
Author: "Jordan Walke", Text: "This is *another* comment" }
We need to get this data into CommentList in a modular way. Modify CommentBox and the
ReactDOM.render() call to pass this data into the CommentList via props:
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
ReactDOM.render(
<CommentBox data={data} />,
document.getElementById('content')
);
Now that the data is available in the CommentList, let's render the comments dynamically:
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment author={comment.Author}>
{comment.Text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
That's it!
Server-side Data
Let's return some data from the server. To do so, we need to first create a C# class to represent
our comments. Right-click on the Models folder (which should be empty), select Add Class,
and enter "CommentModel.cs" as the file name. We'll create a basic comment model:
namespace ReactDemo.Models
{
public class CommentModel
{
public string Author { get; set; }
public string Text { get; set; }
}
}
In a real application, you'd use the repository pattern here, and retrieve the comments from a
database. For simplicity, we'll just modify our controller to have a hard-coded list of comments.
using System.Collections.Generic;
using System.Web.Mvc;
using ReactDemo.Models;
namespace ReactDemo.Controllers
{
public class HomeController : Controller
{
private static readonly IList<CommentModel> _comments;
static HomeController()
{
_comments = new List<CommentModel>
{
new CommentModel
{
Author = "Daniel Lo Nigro",
Text = "Hello ReactJS.NET World!"
},
new CommentModel
{
Author = "Pete Hunt",
Text = "This is one comment"
},
new CommentModel
{
Author = "Jordan Walke",
Text = "This is *another* comment"
},
};
}
public ActionResult Index()
{
return View();
}
}
}
Let's also add a new controller action to return the list of comments:
[OutputCache(Location = OutputCacheLocation.None)]
public ActionResult Comments()
{
return Json(_comments, JsonRequestBehavior.AllowGet);
}
The OutputCache attribute is used here to prevent browsers from caching the response. When
designing a real world API, caching of API requests should be considered more carefully. For
this tutorial it is easiest to simply disable caching.
Finally we add a corresponding route in App_Start\RouteConfig.cs:
using System.Web.Mvc;
using System.Web.Routing;
namespace ReactDemo
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Comments",
url: "comments",
defaults: new { controller = "Home", action = "Comments" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
);
}
}
}
If you hit /comments in your browser, you should now see the data encoded as JSON:
This component is different from the prior components because it will have to re-render itself.
The component won't have any data until the request from the server comes back, at which point
the component may need to render some new comments.
Reactive state
So far, each component has rendered itself once based on its props. props are immutable: they
are passed from the parent and are "owned" by the parent. To implement interactions, we
introduce mutable state to the component. this.state is private to the component and can be
changed by calling this.setState(). When the state is updated, the component re-renders
itself.
render()
getInitialState()
executes exactly once during the lifecycle of the component and sets up the
initial state of the component.
Updating state
When the component is first created, we want to GET some JSON from the server and update the
state to reflect the latest data. We'll use the standard XMLHttpRequest API to retrieve the data. If
you need support for old browsers (mainly old Internet Explorer), you can use an AJAX library
or a multipurpose library such as jQuery. componentWillMount() executes immediately and
only once before the rendering occurs. In the following example, componentWillMount() loads
the data from our XMLHttpRequest and assigns it to the data variable. Finally, it sets the data
variable in state, using setState().
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
componentWillMount: function() {
var xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
All we have done here is move the AJAX call to a separate method and call it when the
component is first loaded and every 2 seconds after that.
Let's also add it to the App_Start\RouteConfig.cs file, like we did earlier for the comments
list:
routes.MapRoute(
name: "NewComment",
url: "comments/new",
defaults: new { controller = "Home", action = "AddComment" }
);
The Form
Now it's time to build the form. Our CommentForm component should ask the user for their name
and comment text and send a request to the server to save the comment.
var CommentForm = React.createClass({
render: function() {
return (
<form className="commentForm">
<input type="text" placeholder="Your name" />
<input type="text" placeholder="Say something..." />
<input type="submit" value="Post" />
</form>
);
}
});
Let's make the form interactive. When the user submits the form, we should clear it, submit a
request to the server, and refresh the list of comments. To start, let's listen for the form's submit
event and clear it.
var CommentForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var author = this.refs.author.value.trim();
var text = this.refs.text.value.trim();
if (!text || !author) {
return;
}
// TODO: send request to the server
this.refs.author.value = '';
this.refs.text.value = '';
return;
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input type="text" placeholder="Say something..." ref="text" />
<input type="submit" value="Post" />
</form>
);
}
});
Events
React attaches event handlers to components using a camelCase naming convention. We attach
an onSubmit handler to the form that clears the form fields when the form is submitted with
valid input.
Call preventDefault() on the event to prevent the browser's default action of submitting the
form.
Refs
We use the ref attribute to assign a name to a child component and this.refs to reference the
component. We can call the value attribute to get the native browser DOM element's value.
Callbacks as props
When a user submits a comment, we will need to refresh the list of comments to include the new
one. It makes sense to do all of this logic in CommentBox since CommentBox owns the state that
represents the list of comments.
We need to pass data from the child component to its parent. We do this by passing a callback
in props from parent to child:
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
var xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
handleCommentSubmit: function(comment) {
// TODO: submit to the server and refresh the list
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
window.setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
Let's call the callback from the CommentForm when the user submits the form:
var CommentForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var author = this.refs.author.value.trim();
var text = this.refs.text.value.trim();
if (!text || !author) {
return;
}
Now that the callbacks are in place, all we have to do is submit to the server and refresh the list:
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
var xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
handleCommentSubmit: function(comment) {
var data = new FormData();
data.append('Author', comment.Author);
data.append('Text', comment.Text);
var xhr = new XMLHttpRequest();
xhr.open('post', this.props.submitUrl, true);
xhr.onload = function() {
this.loadCommentsFromServer();
}.bind(this);
xhr.send(data);
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
window.setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
ReactDOM.render(
<CommentBox url="/comments" submitUrl="/comments/new" pollInterval={2000}
/>,
document.getElementById('content')
);
Congrats!
You have just built a comment box in a few simple steps. The below tweaks are not absolutely
necessary, but they will improve the performance and polish of your application, so we suggest
reading through them :)
We hope you have enjoyed learning about React, and how ReactJS.NET allows you to easily use
it from an ASP.NET MVC web application. Learn more about why to use React and how to
think about React components, or dive into the API reference and start hacking!
Continue on for more awesomeness!
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
window.setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
Now that the bundle has been registered, we need to reference it from the view:
@model IEnumerable<ReactDemo.Models.CommentModel>
@{
Layout = null;
}
<html>
<head>
<title>Hello React</title>
</head>
<body>
<div id="content"></div>
<script src="https://fb.me/react-15.0.1.js"></script>
<script src="https://fb.me/react-dom-15.0.1.js"></script>
@Scripts.Render("~/bundles/main")
@Html.ReactInitJavaScript()
</body>
</html>
That's it! Now if you view the source for the page, you should see a single script tag for the
bundle:
<!-- This is just an example; your URL will be different -->
<script src="/bundles/main?v=OrR8LndNHguz2FwrDeQQg_o3wo7TjIZZnPKxmYJfRs1"></script>
If you go to this URL in your browser, you should notice that the code has been minified, and
both the tutorial code and the Showdown code are in the same file.
In the view, we will accept the list of comments as the model, and use Html.React to render the
component. This will replace the ReactDOM.render call that currently exists in Tutorial.jsx. All
the props from the current ReactDOM.render call should be moved here, and the
ReactDOM.render call should be deleted.
@model IEnumerable<ReactDemo.Models.CommentModel>
@{
Layout = null;
}
<html>
<head>
<title>Hello React</title>
</head>
<body>
@Html.React("CommentBox", new
{
initialData = Model,
url = Url.Action("Comments"),
submitUrl = Url.Action("AddComment"),
pollInterval = 2000,
})
<script src="https://fb.me/react-15.0.1.js"></script>
<script src="https://fb.me/react-dom-15.0.1.js"></script>
<script src="@Url.Content("~/Scripts/showdown.min.js")"></script>
<script src="@Url.Content("~/Scripts/Tutorial.jsx")"></script>
@Html.ReactInitJavaScript()
</body>
</html>
We need to modify the controller action to pass the data to the view:
public ActionResult Index()
{
return View(_comments);
}
That's it! Now if you build and refresh your application, you should notice that the comments
box is rendered immediately rather than having a slight delay. If you view the source of the page,
you will see the initial comments directly in the HTML itself:
<html>
<head>
<title>Hello React</title>
</head>
<body>
<div id="react1">