Click here to Skip to main content
16,016,669 members
Articles / Web Development / React

React Shop - A Tiny E-Commerce

Rate me:
Please Sign up or sign in to vote.
4.99/5 (40 votes)
31 Aug 2016CPOL12 min read 67.5K   3.1K   66   8
An e-commerce application made with Asp.Net MVC, ReactJS, ReactJS.Net and React-Bootstrap

Follow these 3 steps:

  1. Download ReactShop.zip - 1.7 MB (open with Visual Studio 2015 or higher)
  2. Go to Tools > Nuget Package Manager > Manage NuGet Package for Solution...
  3. Click the "Updates" tab. Select all packages to resolve all dependencies.

Image 1

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.

C#
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.

C#
[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:

C#
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

C#
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:

C#
[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.

Image 2

Figure 1. Running unit tests for DiscountManagerTest class.

Product Catalog

Image 3

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.

HTML
@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">
                <!-- Controls -->
                <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">
            <!-- Wrapper for slides -->
            <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.

C#
[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.

C#
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

Image 4

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.

jsx
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) {
        $('.overlay').show();
        $.post('/api/Cart',
        {
            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 () {
            $('.overlay').hide();
        });;
    },
    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={'../' + this.state.SmallImagePath} width="80" height="80" />
                        </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.

Image 5

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:

  • Cart
    • CartItem

ReactJS.Net And Why It Is Awesome

Image 6

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:

Image 7

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 <a href="https://facebook.github.io/react/blog/2015/10/01/react-render-and-top-level-api.html" target="_blank">ReactDOM.render()</a> 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.

C#
@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

Image 8

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...

HTML
<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:

HTML
<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

Image 9

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.

C#
@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.

jsx
        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Instructor / Trainer Alura Cursos Online
Brazil Brazil

Comments and Discussions

 
QuestionNice one.. Pin
Rajesh Pillai17-Dec-16 18:47
Rajesh Pillai17-Dec-16 18:47 
QuestionDid you look at Reflux OR Flux Pin
Sacha Barber6-Sep-16 4:51
Sacha Barber6-Sep-16 4:51 
AnswerRe: Did you look at Reflux OR Flux Pin
Marcelo Ricardo de Oliveira7-Sep-16 12:27
Marcelo Ricardo de Oliveira7-Sep-16 12:27 
GeneralRe: Did you look at Reflux OR Flux Pin
Sacha Barber8-Sep-16 2:49
Sacha Barber8-Sep-16 2:49 
QuestionAnti Forgery Tokens Pin
Phil Martin4-Sep-16 0:55
professionalPhil Martin4-Sep-16 0:55 
PraiseRe: Anti Forgery Tokens Pin
Marcelo Ricardo de Oliveira4-Sep-16 9:37
Marcelo Ricardo de Oliveira4-Sep-16 9:37 
GeneralMy vote of 5 Pin
Florian Rappl31-Aug-16 22:44
professionalFlorian Rappl31-Aug-16 22:44 
Excellent one - looks nice and is already quite functional! Also a quite good introduction to React. So kodus to you as usual!
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira1-Sep-16 2:06
Marcelo Ricardo de Oliveira1-Sep-16 2:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.