Follow these 3 steps:
- Download ReactShop.zip - 1.7 MB (open with Visual Studio 2015 or higher)
- Go to Tools > Nuget Package Manager > Manage NuGet Package for Solution...
- Click the "Updates" tab. Select all packages to resolve all dependencies.
Introduction
In this article you will be able to learn how to apply the React technology to a new or an preexisting ASP.Net MVC application. There aren't many articles out there focusing on integration of React with Asp.Net MVC, so I hope this project helps readers who may be searching for it.
Background
So last month I became an unemployed senior software developer, trying to find my spot in the marketplace, and lately I have spent a lot of time doing specific software projects as part of the job application process. Unfortunately none of these little projects got me hired so far, so the main motivation for me to write this article is to showcase some of the code that I've been researching recently while applying for these jobs. Instead of just throwing the code away, I thought it would be better to share the code with the community and help others solve some of the problems I've found in the way.
Software Requirements
For the development of this article, I used:
- SQL Server Express (download it here)
- Visual Studio Community 2015 Update 3 or superior (download it here)
- Asp.Net MVC 5 ASP.NET MVC 5 is a framework for building scalable, standards-based web applications using well-established design patterns and the power of ASP.NET and the .NET Framework
- Microsoft ASP.NET Web Optimization Framework (via nuget) ASP.NET Optimization introduces a way to bundle and optimize CSS and JavaScript files.
- Web Analyzer (https://visualstudiogallery.msdn.microsoft.com/6edc26d4-47d8-4987-82ee-7c820d79be1d) Provides static analysis directly in Visual Studio for JavaScript, TypeScript, JSX, CSS and more
- Web Extension Pack (https://visualstudiogallery.msdn.microsoft.com/f3b504c6-0095-42f1-a989-51d5fc2a8459) The easiest way to set up Visual Studio for the ultimate web development experience.
- ReactJS.NET Core (via nuget) React.js and Babel tools for .NET. Important: This package does not do much on its own; you probably want an integration package (like React.Web.Mvc4) as well. Please refer to project site (http://reactjs.net/) for more details, usage examples and sample code.
- ReactJS.NET (MVC 4 and 5) (via nuget)
- ReactJS.NET - Babel for ASP.NET Web Optimization Framework (via nuget) Allows you to transpile JavaScript via Babel in the ASP.NET Web Optimization Framework.
- showdown (via nuget) Showdown is a javascript port of Markdown (markup language).
Code First - Entity Framework
For this small project I chose the Code First technology that is provided by Entity Framework 6 to create a Context
, which maps our entities (C# classes/properties/types) to the database objects (tables/columns/data types). Even the database creation is provided by Code First, so we can avoid any Transact-SQL scripts in this project.
public class Context : DbContext
{
public DbSet<CartItem> CartItem { get; set; }
public DbSet<Product> Product { get; set; }
}
Listing 1. Context.cs file showing the Entity Framework Code-First context class.
The data entity set that generate our database is rather minimalist, comprising only the CartItem
and the Product
entities. Everything else (Customer info, total, subtotal, discount, etc.) is either hard coded in the backend, or automatically calculated from the CartItem
values.
[Table("Product")]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string SKU { get; set; }
public string Description { get; set; }
public string SmallImagePath { get; set; }
public string LargeImagePath { get; set; }
public decimal Price { get; set; }
}
[Table("CartItem")]
public class CartItem
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Product")]
public int ProductId { get; set; }
[Required]
public virtual Product Product { get; set; }
public int Quantity { get; set; }
}
Listing 2. The two entities of our project: Product and CartItem.
When the application starts, it checks whether the SQL Server database defined in the connectionstring already exists. If not, it uses Code First described above to create a new database with the CartItem
and Product
tables and populate the products accordingly:
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
...
var checkoutManager = AutoFacHelper.Resolve<icheckoutmanager>();
checkoutManager.InitializeDB();
}
}
</icheckoutmanager>
Listing 3. Database initialization in Global.asax.cs
public Context InitializeDB()
{
var db = new Context();
if (!db.Database.Exists())
{
db.Database.CreateIfNotExists();
var products = new string[]
{
"10 Million Member CodeProject T-Shirt|3399",
"Women's T-Shirt|3399",
"CodeProject.com Body Suit|1399",
"CodeProject Mug Mugs|1099",
"RootAdmin Mug|1099",
"Drinking Glass|1099",
"Stein|1399",
"Mousepad|1099",
"Square Sticker|299",
};
var index = 1;
foreach (var p in products)
{
var description = p.Split('|')[0];
var price = decimal.Parse(p.Split('|')[1]) / 100M;
var product =
db.Product.Add(new Product
{
SKU = Guid.NewGuid().ToString(),
SmallImagePath = string.Format("Images/Products/small_{0}.jpg", index),
LargeImagePath = string.Format("Images/Products/large_{0}.jpg", index),
Description = description,
Price = price
});
var cartItem =
db.CartItem.Add(new CartItem
{
Product = product,
Quantity = 1
});
index++;
}
db.SaveChanges();
}
return db;
}
Listing 4. Database setup in CheckoutManager.cs
React.js
React is a JavaScript library for building user interfaces that was created by Facebook. While other JavaScript frameworks/libraries implement a MV* pattern (like MVVM as in Knockjout JS or MVC as in Angular JS), React is a library that focus only on the V part. Instead of being a full featured JavaScript like Backbone, the goal of React JS is to do only one thing, and do it well.
If you ask most of the tech gurus about what are the hot trends in 2016/2017 in the field of programming (such as the ThoughtWorks' Technology Radar), they most probably will mention React.js.
Quote:
In the avalanche of front-end JavaScript frameworks, React.js stands out due to its design around a reactive data flow. Allowing only one-way data binding greatly simplifies the rendering logic and avoids many of the issues that commonly plague applications written with other frameworks. We're seeing the benefits of React.js on a growing number of projects, large and small, while at the same time we continue to be concerned about the state and the future of other popular frameworks like AngularJS. This has led to React.js becoming our default choice for JavaScript frameworks.
Unit Testing
The unit tests in the application were designed to match the following business requirements:
- Purchases between $500.00 and $599.99 should grant a discount rate of 5 %
- Purchases between $600.00 and $699.99 should grant a discount rate of 10 %
- Purchases above $700.00 should grant a discount rate of 15 %
Notice how each limit value is tested against the requirements table above:
[TestClass]
public class DiscountManagerTest
{
public DiscountManager discountManager;
[TestInitialize]
public void Initialize()
{
discountManager = new DiscountManager();
}
[TestMethod]
public void GetDiscount_0_Should_Return_Rate_0_And_Value_0()
{
var rule = discountManager.GetDiscount(0);
Assert.AreEqual(0, rule.Rate);
Assert.AreEqual(0, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_499_99_Should_Return_Rate_0_And_Value_0()
{
var rule = discountManager.GetDiscount(499.99M);
Assert.AreEqual(0, rule.Rate);
Assert.AreEqual(0, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_500M_Should_Return_Rate_5_And_Value_25()
{
var rule = discountManager.GetDiscount(500M);
Assert.AreEqual(.05M, rule.Rate);
Assert.AreEqual(25, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_599_99M_Should_Return_Rate_5_And_Value_25()
{
var rule = discountManager.GetDiscount(599.99M);
Assert.AreEqual(.05M, rule.Rate);
Assert.AreEqual(30M, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_600M_Should_Return_Rate_10_And_Value_60()
{
var rule = discountManager.GetDiscount(600M);
Assert.AreEqual(.10M, rule.Rate);
Assert.AreEqual(60M, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_699_99M_Should_Return_Rate_10_And_Value_70M()
{
var rule = discountManager.GetDiscount(699.99M);
Assert.AreEqual(.10M, rule.Rate);
Assert.AreEqual(70M, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_700M_Should_Return_Rate_15_And_Value_105()
{
var rule = discountManager.GetDiscount(700M);
Assert.AreEqual(.15M, rule.Rate);
Assert.AreEqual(105M, rule.CalculatedDiscount);
}
[TestMethod]
public void GetDiscount_10000M_Should_Return_Rate_15_And_Value_1500()
{
var rule = discountManager.GetDiscount(10000M);
Assert.AreEqual(.15M, rule.Rate);
Assert.AreEqual(1500M, rule.CalculatedDiscount);
}
Listing 5.The contents of the DiscountManagerTest.cs.
<img height="564" src="1121533/UnitTests.png" width="465" />
Figure 1. Running unit tests for DiscountManagerTest class.
Product Catalog
<img height="461px" src="1121533/catalog.png" width="600px" />
Figure 2. The Product Catalog view.
The Product Catalog view features a simple Product Carousel control. If you search for product carousel using Bootstrap, you will end up finding similar solutions such as this.
The product carousel is great because of the endless animation and the ability to show many products using only a fraction of the page space.
The Product Carousel is rendered on the server side via Razor view engine. The carousel displays four products at once, so the code in the Index.cshtml view defines a foreach
loop that iterates over "pages" of 4 products each.
@using ReactShop.Core;
@model List<ReactShop.Core.DTOs.ProductDTO>
@{
ViewBag.Title = "ReactShop";
}
<div class="container">
<div class="row">
<div class="row">
<div class="col-md-9">
<h3>
Product Catalog
</h3>
</div>
<div class="col-md-3">
<!---->
<div class="controls pull-right hidden-xs">
<a class="left fa fa-chevron-left btn btn-success" href="#carousel-example"
data-slide="prev"></a><a class="right fa fa-chevron-right btn btn-success"
href="#carousel-example"
data-slide="next"></a>
</div>
</div>
</div>
<div id="carousel-example" class="carousel slide hidden-xs" data-ride="carousel">
<!---->
<div class="carousel-inner">
@foreach (var pageIndex in Enumerable.Range(0, (Model.Count() - 1) / 4))
{
<div class="item@(pageIndex == 0 ? " active" : "")">
<div class="row">
@using(Html.BeginForm("AddToCart", "Home"))
{
@Html.AntiForgeryToken()
foreach (var product in Model.Skip(pageIndex * 4).Take(4))
{
<div class="col-sm-3">
<div class="col-item">
<div class="photo">
<img src="~/@product.SmallImagePath" class="img-responsive"
alt="a" width="350" height="260" />
</div>
<div class="info">
<div class="row">
<div class="price col-md-6">
<h5 class="truncate">
@product.Description
</h5>
<h5 class="price-line">
$<span class="price-text-color">@product.Price</span>
</h5>
</div>
</div>
<div class="separator clear-left">
<p class="btn-add">
<button type="submit" class="btn btn-link" name="SKU"
value="@product.SKU">
<i class="fa fa-shopping-cart" aria-hidden="false"></i>
Add to Cart
</button>
</p>
<p class="btn-details">
<button type="button" class="btn btn-link" name="SKU"
value="@product.SKU">
<i class="fa fa-list"></i><a href=""
class="hidden-sm"></a>
Details
</button>
</p>
</div>
<div class="clearfix">
</div>
</div>
</div>
</div>
}
}
</div>
</div>
}
</div>
</div>
</div>
</div>
Listing 6.The contents of the Index.cshtml view.
Adding to Cart
Notice the use of the Anti-Forgery Token. This is a security measure provided by the Asp.Net MVC as a means to protect you application against cross-site request forgery attacks (CSRF). The Anti-Forgery Token is passed to the client page and then passed back to the submit post action received by the controller to ensure that the request is made by a legitimate client. Remember to never trus anyone and always use AntiForgeryToken
in your applications.
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult AddToCart(string SKU)
{
checkoutManager.SaveCart(new Core.DTOs.CartItemDTO
{
SKU = SKU,
Quantity = 1
});
return RedirectToAction("Cart", "Home");
}
Listing 5. Code snipped from HomeController.cs showing the ValidateAntiForgeryToken
attribute on the AddToCart
method.
The CartItemDTO
will then be passed to the SaveCart
method of CheckoutManager
class, that will perform the database operation (update or delete) based on the quantity provided in the CartItemDTO
object.
public void SaveCart(CartItemDTO newOrEditItem)
{
try
{
if (newOrEditItem.Quantity < 0)
newOrEditItem.Quantity = 0;
using (var db = new Context())
{
var product = db.Product.Where(p => p.SKU == newOrEditItem.SKU).Single();
var cartItem =
(from ci in db.CartItem
join p in db.Product on ci.ProductId equals p.Id
where p.SKU == newOrEditItem.SKU
select ci)
.SingleOrDefault();
if (cartItem != null)
{
if (newOrEditItem.Quantity == 0)
db.CartItem.Remove(cartItem);
else
{
cartItem.Quantity = newOrEditItem.Quantity;
cartItem.Product = product;
}
}
else
{
db.CartItem.Add(new CartItem
{
Product = product,
Quantity = newOrEditItem.Quantity
});
}
db.SaveChanges();
}
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}",
validationError.PropertyName,
validationError.ErrorMessage);
}
}
}
}
Listing 8. Code snippet showing SaveCart
method of CheckoutManager
class.
Shopping Cart
<img height="517" src="1121533/Cart.png" width="600" />
Figure 3. The Shopping Cart view.
Compared to the Product Catalog, the Cart Page is rendered in a very different way. First, the Razor engine is not used to directly render the view. Instead, Razor calls the React
method of the React.Web.Mvc.HtmlHelperExtensions class and passes the model to it. The Razor in turn renders the CartView
that has been declared as a React component.
var CartItem = React.createClass({
getInitialState: function () {
var item = this.props.model;
return {
SKU: item.SKU,
SmallImagePath: item.SmallImagePath,
LargeImagePath: item.LargeImagePath,
Description: item.Description,
SoldAndDeliveredBy: item.SoldAndDeliveredBy,
Price: item.Price,
Quantity: item.Quantity,
Subtotal: item.Subtotal
};
},
updateState: function (change) {
this.setState(Object.assign({}, this.state, change))
},
handleIncrement: function () {
this.postQuantity(this.state.Quantity + 1);
},
handleDecrement: function () {
this.postQuantity(this.state.Quantity - 1);
},
removeItem: function () {
this.postQuantity(0);
},
postQuantity: function (quantity, callback) {
$( $.post( {
SKU: this.props.model.SKU,
Quantity: quantity,
Price: this.props.model.Price
})
.done(function (data) {
for (var item of data.CartItems) {
if (item.SKU == this.props.model.SKU) {
this.updateState({ Quantity: item.Quantity, Subtotal: item.Subtotal });
this.props.handleCartChange(data, item);
return;
}
}
}.bind(this))
.always(function () {
$( });;
},
handleQuantityChanged: function (event) {
var newQty = 1;
var val = event.target.value;
if (val && !isNaN(val))
newQty = parseInt(val);
this.postQuantity(newQty);
},
render: function () {
return (
<Row className="vertical-align">
<Column md={2} className="justify-left">
<Row className="fullwidth">
<Column md={3}>
<img src={ </Column>
</Row>
</Column>
<Column md={4} className="justify-left">
<Row className="fullwidth">
<Column md={9}>
<span>{this.state.Description}</span>
</Column>
</Row>
</Column>
<Column md={2} className="green justify-center">
<Dollars val={this.state.Price } />
</Column>
<Column md={2} className="justify-center">
<div className="text-center">
<ButtonGroup>
<input type="button" className="btn btn-default" value="-" onClick={this.handleDecrement} />
<input type="text" className="btn" value={this.state.Quantity} onChange={this.handleQuantityChanged } />
<input type="button" className="btn btn-default" value="+" onClick={this.handleIncrement} />
</ButtonGroup>
<a onClick={this.removeItem} className="remove pointer">Remove</a>
</div>
</Column>
<Column md={2} className="green justify-right">
<Dollars val={this.state.Subtotal} />
</Column>
</Row>
);
}
})
class CartView extends React.Component {
constructor(props) {
super(props);
this.state = {};
var items = [];
for (var i = 0; i < this.props.model.CartItems.length; i++) {
var item = this.props.model.CartItems[i];
items.push({
SKU: item.SKU,
SmallImagePath: item.SmallImagePath,
LargeImagePath: item.LargeImagePath,
Description: item.Description,
SoldAndDeliveredBy: item.SoldAndDeliveredBy,
Price: item.Price,
Quantity: item.Quantity,
Subtotal: item.Subtotal
});
}
this.state = {
canFinishOrder: true,
items: items,
Subtotal: this.props.model.Subtotal,
DiscountRate: this.props.model.DiscountRate,
DiscountValue: this.props.model.DiscountValue,
Total: this.props.model.Total
};
}
handleCartChange(cart, cartItem) {
var newState = Object.assign({}, this.state, {
Subtotal: cart.Subtotal,
DiscountRate: cart.DiscountRate,
DiscountValue: cart.DiscountValue,
Total: cart.Total
});
if (cartItem.Quantity == 0) {
newState.items.splice(newState.items.findIndex(i =>
i.SKU == cartItem.SKU), 1);
}
this.setState(newState);
}
render() {
const header = (<Row className="vertical-align">
<Column md={6} className="justify-left">item(s)</Column>
<Column md={2} className="justify-center">unit price</Column>
<Column md={2} className="justify-center">quantity</Column>
<Column md={2} className="justify-right">subtotal</Column>
</Row>);
const body = (this.state.items.map(item => {
return <CartItem key={item.SKU} model={item}
handleCartChange={this.handleCartChange.bind(this)} />;
}
));
const footer = (<Row>
<Column md={7}></Column>
<Column md={5} className="my-children-have-dividers">
<Row className="vertical-align">
<Column md={8} className="justify-right">
Subtotal ({this.state.items.length} <Pluralize value={this.state.items.length} singular="item" plural="items" />):
</Column>
<Column md={4} className="green justify-right">
<span>
<Dollars val={this.state.Subtotal} />
</span>
</Column>
</Row>
{ this.state.DiscountRate
?
<Row className="vertical-align">
<Column md={8} className="justify-right">
Discount (<span>{this.state.DiscountRate}</span>%):
</Column>
<Column md={4} className="green justify-right">
<span>
<Dollars val={this.state.DiscountValue} />
</span>
</Column>
</Row>
: null
}
<Row className="vertical-align">
<Column md={12} className="justify-right">
<h3>
Total:
<span className="green">
<Dollars val={this.state.Total} />
</span>
</h3>
</Column>
</Row>
</Column>
</Row>);
return (
<div className="cart">
{
this.state.items.length == 0 ? null :
<div>
{/* TITLE */}
<h3>Your shopping cart ({ this.state.items.length} <Pluralize value={this.state.items.length} singular="item" plural="items" />)</h3>
{/* NAVIGATION BUTTONS */}
<Row>
<Column md={3}>
<a href={this.props.urlNewProduct}>
<button type="button" className="btn btn-success">Add new product</button>
</a>
</Column>
<Column md={3} className="pull-right">
<a href={this.props.urlCheckoutSuccess}>
<button type="button" className="btn btn-success pull-right">Proceed to checkout</button>
</a>
</Column>
</Row>
{/* NAVIGATION BUTTONS */}
<br />
{/* CART PANEL */}
<Panel header={header} footer={footer}>
{body}
</Panel>
{/* CART PANEL */}
{/* NAVIGATION BUTTONS */}
<Row>
<Column md={3}>
<a href={this.props.urlNewProduct}>
<button type="button" className="btn btn-success">Add new product</button>
</a>
</Column>
<Column md={3} className="pull-right">
<a href={this.props.urlCheckoutSuccess}>
<button type="button" className="btn btn-success pull-right">Proceed to checkout</button>
</a>
</Column>
</Row>
{/* NAVIGATION BUTTONS */}
</div>
}
{
this.state.items.length > 0
? null
:
<div>
<h1><br /><br />:(</h1>
<div>
<h1>
Oops! Your shopping cart is empty.
</h1>
<br />
<div className="empty-cart-content-message">
Enter more products and resume shopping.
</div>
<br />
<div>
{
this.state.canFinishOrder
?
<a href={this.props.urlNewProduct}>
<button type="button" className="btn btn-success">Enter new product</button>
</a>
: null
}
</div>
</div>
</div>
}
</div>
);
}
}
Listing 9.The contents of the Cart.jsx file.
JSX file
At first the JSX file looks a little weird when you are not used to it. It looks like a mix of JavaScript and HTML in the same place. Now compare it to way Angular JS works. Angular seems to bring JavaScript to HTML, while React seems to bring HTML to JavaScript.
When you see JSX file, you may be inclined to think that those HTML chunks are injected directly into the HTML page. But in reality, this is not what happens. Instead, those HTML parts are "transpiled" into real JavaScript code. In fact, that HTML markup is just a different representation of JavaScript. In the end, the JSX code is deployed to the client as plain old JavaScript code, and those HTML tags are rendered as a structured chain of React.createElement
methods. Just imagine the huge amount of repetitive work of creating all those elements one by one and by hand. And that's why in the end, React is nothing but pure JavaScript.
<img height="1000" src="1121533/jsx.png" width="423" />
Figure 4. How jsx is "transpiled" into code. In the end, it's all JavaScript.
Extending React.Component
React allows you to create complex views by breaking them into smaller components.
Where the CartItem component represents each line in the cart (showing product description, quantity and price) and the Cart component comprises all the rest.
Each React component must implement the render
function, which in turn must return a tree of React components. This tree of components returned by render function may be:
- a very simple component, such as the code to render a single div HTML tag,
- a complex component, such as a whole page or
- a null value
In our case, the only components of our Cart view are:
ReactJS.Net And Why It Is Awesome
We .Net developers are lucky to have Facebook showing love for our platform. ReactJS.Net is a package that integrates Asp.Net MVC with React on the backend, allowing server-side rendering as well as bundling and minification of the JavaScript code.
ReactJS.Net provides On-the-fly JSX to JavaScript compilation. This means that any file with the .jsx extension will be automatically compiled to JavaScript and cached on the server-side, without the need of precompilation. See what happens when you browse the CheckoutSuccess.jsx file directly:
<img height="730px" src="1121533/onthefly.png" width="600px" />
Figure 5. Compiled JavaScript created automatically from CheckoutSuccess.jsx, thanks to ReactJS.NET
ReactJS.NET also provides Server-side component rendering. In a usual scenario, we would create our .jsx components for our Cart view, and that would be sent to the browser. The browser in turn would either take some preloaded JSON objects representing the cart items or fetch data the cart data via AJAX call and then call the ReactDOM.render()
to render the cart with the initial state. But with the help of ReactJS.NET, we can pre-render the initial state of the cart (that is, items, values, discount and total) already on the server-side, because ReactJS.NET understands the syntax of React components of the .jsx files, and knows how to process the JavaScript expressions (that is, the expressions inside curly braces ({}) ) injecting the Model
and rendering the initial state of the component(s) before they are sent to the browser. This approach is quite convenient and in many cases can minimize the use of a view engine such as Razor or aspx.
@Html.React("CheckoutSuccessView", new
{
title = "React JS.Net + React-Bootstrap",
model = Model
})
Listing 10.The model is passed to the CheckoutSuccessView
component and ReactJS.NET pre-render everything on the server side. This is absolutely fantastic!
React-Bootstrap
In order to push further the concept of componentization and reutilization, fortunately we have the entire Bootstrap framework rebuilt for react, at our disposal, thanks to the formidable work of Jimmy Jia and his React-Bootstrap project, which is still in alpha release, but contains a complete set of components
Everyone who has worked with Bootstrap knows that, although it is a very useful web design toolbox, it also becomes sometimes difficult to read and to write, because in the end it becomes an endless tree of div
tags, with many different css classes that are difficult to memorize.
This is where React-Bootstrap kicks in. Just take a look at the following React-Bootstrap snipped extracted from JSX...
<Row className="vertical-align">
<Column md={2} className="justify-left">
<Row className="fullwidth">
<Column md={3}>
<img src={'../' + this.state.SmallImagePath} width="80" height="80" />
</Column>
</Row>
</Column>
...and compare it to the regular HTML Bootstrap markup that would be required to write the same view:
<div class="row vertical-align">
<div class="col-md-2 justify-left">
<div class="row fullwidth">
<div class="col-md-3">
<img src="../Images/Products/small_7.jpg" width="80" height="80"></div>
</div>
</div>
Props vs State
One of the most frequently askes questions about React implementation is: "how do I know when to use props or state?"
Each React component works with two special obejcts: "props" (from "properties") and "state". They both are used as raw data from which the rich HTML will be rendered in the page.
We can find an in-depth explanation about the differences between props
and state
here:
Question |
props |
state |
Can get initial value from parent Component? |
Yes |
Yes |
Can be changed by parent Component? |
Yes |
No |
Can set default values inside Component? |
Yes |
Yes |
Can change inside Component? |
No |
Yes |
Can set initial value for child Components? |
Yes |
Yes |
Can change in child Components? |
Yes |
No |
Source: https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
From React doc:
Quote:
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.
Custom components
In this project we implement two small components, which work as helpers and don't do much, but help our other main components from getting cluttered and unreadable.
Updating quantities
The quantities of the cart items can be updated in 4 different ways:
- By clicking the decrease button
- By clicking the increase button
- By typing the quantity directly in the text box
- By clicking the remove link
Each of these actions will trigger the postQuantity
function that invokes the /api/Cart Web Api method, which in turn changes the quantities in the database and return a new snapshot of the cart state. At the end of the post ajax method, the postQuantity
function updates the state
of the view by calling the updateState
function, passing the new Cart data as the new state.
In case of product removal, items are removed by changing the quantity of the item to zero. This will trigger the same flow as shown above. The only difference is that at the end of the update the cart item will be removed from the component state.
Checkout details
<img height="593" src="1121533/checkoutsuccess.png" width="600" />
Figure 4. The Checkout Success view.
Although the CheckoutSuccess
view does not contain any interaction (besides the "back to the product catalog" button), it was implemented entirely as a single React component. The reason is that we could take advantage of the simple syntax provided by the components of the React-Bootstrap library that we already explained above. All the binding values are passed via props
and there is no need to use React's state
object.
@using System.Web.Optimization
@using System.Collections.Generic
@model ReactShop.Core.DTOs.CheckoutSummaryDTO
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
ViewBag.Title = "ReactShop";
}
@Html.React("CheckoutSuccessView", new
{
title = "React JS.Net + React-Bootstrap",
model = Model
})
@Scripts.Render(" ~/bundles/js")
Listing 10.The contents of the CheckoutSuccess.cshtml file.
class CheckoutSuccessView extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<br />
<Row>
<span className="fa fa-check-circle"></span>
</Row>
<h3 className="text-center">
<span>Your order has been received. Thank you for your purchase!</span>
</h3>
<h4 className="text-center">
<span>Your order # is:</span>
<span className="green">{this.props.model.OrderNumber}</span>
</h4>
<br />
<h3>
Order Information
</h3>
<Panel>
<Row>
<Column md={6}>
<h4>
<span>Order No:</span>
<span className="green">{this.props.model.OrderNumber}</span>
</h4>
<p>
You will receive a confirmation e-mail with the details of your order. Please verify your AntiSpam settings of your e-mail provider.
</p>
</Column>
<Column md={6}>
<h4>Payment term</h4>
<div className="boleto">
<p><i className="fa fa-paypal leading-icon" aria-hidden="true"></i> Paypal</p>
<p className="offset30"><Dollars val={this.props.model.Total} /></p>
</div>
</Column>
</Row>
<Row className="gray row-eq-height border-top border-bottom">
<Column md={3}>
<h4><span className="fa fa-user leading-icon"></span>Your info</h4>
<p className="offset30">{this.props.model.CustomerInfo.CustomerName}</p>
<p className="offset30">{this.props.model.CustomerInfo.PhoneNumber}</p>
</Column>
<Column md={3} className="border-right">
<br />
<br />
<p>{this.props.model.CustomerInfo.Email}</p>
</Column>
<Column md={6}>
<h4><span className="fa fa-home leading-icon"></span>Shipping address</h4>
<p className="offset30">{this.props.model.CustomerInfo.DeliveryAddress}</p>
</Column>
</Row>
<Row className="gray">
<Column md={6}>
<h4><span className="fa fa-gift leading-icon"></span>Delivery</h4>
</Column>
<Column md={6}>
<br />
<p className="float-right">
Delivery time is {this.props.model.DeliveryUpTo} days
</p>
</Column>
</Row>
<Row className="gray">
<Column md={6}>
<p className="offset30"><b>Product description</b></p>
</Column>
<Column md={6} className="pull-right">
<p><b className="float-right">Quantity</b></p>
</Column>
</Row>
{ this.props.model.CartItems.map(item =>
<Row className="gray">
<Column md={6}>
<div className="offset30 truncate">
<span>•</span>
<span>{item.Description}</span>
</div>
</Column>
<Column md={6} className="pull-right">
<p className="float-right">{item.Quantity}</p>
</Column>
</Row>
)
}
</Panel>
<Row>
<Column md={9}></Column>
<a href="/">
<Column md={2}>
<Button bsStyle="success">Back to product catalog</Button>
</Column>
</a>
</Row>
</div>
);
}
}
Listing 11.The contents of the CheckoutSuccess.jsx file.
Final considerations
If you took your time and patience to reach this line, thank you so much for your reading! If you have any complaint or suggestion about the code or the article, please let me know. Don't forget leaving your opinion in the comments section below.
History
2016/08/31: first version
2016/09/01: jsx files explained
2016/09/04: server-side rendering explained