OpinionT2 Light_1200x303

A comparison of Blazor and React when it comes to modern web development—a review of the pros and cons. Does Blazor stack up?

Blazor WebAssembly (or Blazor Wasm, as all the cool kids are calling it!) has arrived, but how does it stack up compared to the other, more established options for building "modern" web applications?

Let's start with a comparison to React JS.

Note: Interested in how Blazor stacks up against Angular or Vue instead? Check out a comparison of Blazor and Angular here and a comparison of Blazor and Vue here.

Specifically we'll explore how the following aspects work (for both frameworks):

  • Getting started
  • Building the UI
  • Passing data between components
  • Handling Forms
  • Routing
  • Fetching data from an API
  • Pros and cons of each

Before we dig in, it's worth noting this article focuses on Blazor Wasm, which runs in the browser using WebAssembly. That said, many of the points are equally valid if you're looking at using Blazor Server instead.

React—The Two-Minute Overview

React is a UI library which helps you build responsive, interactive user interfaces for your web-based applications.

Unlike frameworks such as Angular, React focuses purely on the component-building aspects of your app and doesn't tackle other concerns (like routing), preferring instead to leave it up to you to choose the best tool for your specific use case.

When you create a new React app, it will automatically bring in various packages it needs to work (using npm or its alternative, Yarn) and you can then bring in additional packages to handle things like routing, handling forms, making network requests.

You write your application using HTML (well, actually JSX but we'll get to that), CSS, and JavaScript.

To publish your app, you can run a command to bundle it up (using a bundler such as webpack), take the static bundled files (HTML, JavaScript, etc.) and deploy them to a web server.

When someone visits the deployed site, the browser JavaScript engine kicks in and runs your application code, rendering elements via the browser's DOM and handling various browser events (users clicking buttons, entering text in an input, etc.).

If you need to fetch data from a database or similar, you can make HTTP requests from your application to a server, which will return the data for your app to display.

How Does Blazor Compare?

Blazor is a framework which also enables you to build client web applications that run in the browser, but using C# instead of JavaScript.

When you create a new Blazor app, it arrives with a few carefully selected packages (the essentials needed to make everything work) and you can install additional packages using NuGet.

From here, you build your app as a series of components, using the Razor markup language, with your UI logic written using C#.

To publish your app, you can use .NET's built-in publish command, which bundles up your application into a number of files (HTML, CSS, JavaScript and DLLs) which can then be published to any web server that can serves static files.

When a user accesses your Blazor Wasm application, a Blazor JavaScript file takes over which downloads the .NET runtime, your application and its dependencies, before running your app using WebAssembly.

Blazor then takes care of updating the DOM, rendering elements and forwarding events (such as button clicks) to your application code.

Creating a New React App

By far the simplest way to spin up a new React application is to use "Create React App."

You need these commands:

npx create-react-app my-app
cd my-app
npm start

CRA also gives you easily accessed commands for bundling up your app ready for deployment.

Creating a New Blazor App

For Blazor, you can use Visual Studio or spin up a new project via the command prompt.

dotnet new blazorwasm
cd blazorwasm
dotnet run

You have a few other options, like the ability to include infrastructure for authenticating users, and whether to host your Blazor app in an ASP.NET web application, but the command above is the simplest option to get started.

Building Your UI with React

With React you build your applications as a series of components, using a declarative approach.

Here's an example:

import React, { useState} from 'react';

export default function Greeting() {
    const [name, setName] = useState();

    const nameChanged = (e) => {
        setName(e.target.value)
    }

    return (<>
        <label>What's your name?</label>
        <input type="text" onChange={nameChanged} placeholder="Bob"/>
        <span>Hello {name}</span>
    </>)
}

If you're not familiar with React, there's a little to unpack here.

React components are written using JavaScript.

Here we export a JavaScript function called Greeting.

This Greeting function represents a React component, and exporting it means we can easily reference this elsewhere in our app (so we can render it wherever we want).

Our Greeting function returns something which looks suspiciously like HTML but is actually JSX.

JSX is really JavaScript.

For example, when we declare span as JSX in a component, React will turn this into JavaScript which will be invoked to render the relevant HTML markup in the browser.

In practice, you can usually just write your JSX as if it were HTML and everything "just works!"

If you look at the input element, you'll notice we've declared an event handler for the onChange event. When you type something into this input, it will invoke our nameChanged function.

This will update the state of our component using something called hooks. Specifically we're using the UseState hook to update the value of name to whatever the user typed in.

Finally, in our span element we render the value of name resulting in a friendly personalized greeting for our user.

A form field asks "What's your name," and Jon has been entered. After that, it says, "Hello Jon"

As we change the value in the input, the greeting instantly updates to reflect the new value.

Generally speaking, everything you build in React revolves around components, which you can then render wherever you want, like this:

<h1>
    A brief introduction to React...
</h1>

<Greeting />

React example showing how typing name changes the greeting

In summary, a React UI:

  • Comprises one or more components
  • Is written using JSX (which takes your markup and data, and combines them together)
  • Runs as JavaScript in the browser

Building Your UI with Blazor

Blazor adopts a very similar approach to React in that you build your UI using components.

Where it differs is that you use Razor and C# (instead of JSX and JavaScript) to write your markup and UI logic.

<label>What's your name?</label>
<input type="text" @bind-value="Name" @bind-value:event="oninput" placeholder="Bob"/>
<span>Hello @Name</span>

@code {
    public string Name { get; set; }
}

This operates exactly the same way as the React example when you run it in the browser.

We've got roughly the same markup, but this time we have used Blazor's @bind syntax to bind our input to a property called Name.

When the user enters their name, the Name property will be updated with the value they enter.

By default, Blazor would update the value of Name on blur (when we clicked out of the text input) so we've added @bind-value:event="oninput" to make it update the property as soon as we start typing.

As with React you're now free to render this component wherever (and as often as) you like.

<h1>
    A brief introduction to Blazor...
</h1>

<Greeting />

Blazor example showing how typing in a name changes the greeting

In summary, a Blazor UI:

  • Comprises one or more components
  • Is written using Razor and C# (which takes your markup and data, and combines them together)
  • Runs on WebAssembly in the browser

Passing Data Around—React

There are two main "out of the box" approaches to handling state in your React components.

They can either interact with their own state (as we saw when we stored Name in our example), or they can accept data via props:

export default function Greeting(props) {
    const [name, setName] = useState();

    const nameChanged = (e) => {
        setName(e.target.value)
    }

    return (<>
        <h2>{props.headline}</h2>
        <label>What's your name?</label>
        <input type="text" onChange={nameChanged} placeholder="Bob"/>
        <span>Hello {name}</span>
    </>)
}

Here we've added a props argument to our Greeting function, and then rendered its value in our component via {props.headline}.

Now when we render this component, we can pass in a value for the headline.

<Greeting headline="Welcome, it's great to see you again"/>

React example showing greeting with customized greeting

As you use React for real applications, these options for storing state can become a little unwieldy, with more and more state to pass around and/or store. This is where alternatives like Redux come into play, giving you a centralized data store for your entire application.

But it's worth noting Redux is not directly tied to React, and is not something you would necessarily use from day one when you start building React applications.

Passing Data Around—Blazor

Broadly speaking, Blazor has the same two primary options for managing state.

You can store data in the component itself using properties (as with Name in our example) or take data in via parameters.

<h2>@Headline</h2>
<label>What's your name?</label>
<input type="text" @bind-value="Name" @bind-value:event="oninput" placeholder="Bob"/>
<span>Hello @Name</span>

@code {    
    [Parameter]
    public string Headline { get; set; }

    public string Name { get; set; }
}

As with the React example, when you render Greeting you can pass in a headline and it will be rendered accordingly.

<Greeting Headline="Welcome, it's still great to see you..."/>

Handling Forms in React

React doesn't ship with anything to specifically help you with forms.

This means you need to either "roll your own" or use a third-party npm package for the job.

Here's the markup for a simple contact form.

return (
    <form onSubmit={handleSubmit}>
        <label>
            Name:
            <input type="text" value={name} onChange={handleNameChange} />
        </label>
        <label>
            Thoughts?:
            <input type="text" value={comments} onChange={handleCommentsChange} />
        </label>
        <input type="submit" value="Submit"/>
    </form>
)   

Note how we're setting the value of our inputs from state in our component (name and comments) and forwarding the onChange events to our own functions (handleNameChange and handleCommentsChange).

Here's what the JavaScript for this function looks like:

const [name, setName] = useState();
const [comments, setComments] = useState();

function handleNameChange(event){
    setName(event.target.value);
}

function handleCommentsChange(event){
    setComments(event.target.value);
}

function handleSubmit(event){
    // submit data to your server
    alert(`${name}: ${comments}`)
    event.preventDefault();
}

So we are effectively intercepting the normal behavior for the form to both read and update our own component state.

For validation you can either roll your own, writing logic to check the values set in the form and alerting the user if they are invalid, or lean on third-party libraries (typically brought in via npm).

Handling Forms with Blazor

By contrast, Blazor has built-in functionality to handle your form data and validation using something called EditForm.

Here's the markup for an equivalent form to our React example.

@using System.ComponentModel.DataAnnotations

<EditForm Model="FormModel" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <label for="name">
        Name:
        <InputText id="name" @bind-Value="FormModel.Name"/>
    </label>
    <label for="slug">
        Thoughts?:
        <InputText id="comments" @bind-Value="FormModel.Comments"/>
    </label>
    <input type="submit" value="Submit"/>
    <ValidationSummary />
</EditForm>

In many ways this is similar to React, except we're using Blazor's InputText component for our input fields.

Technically it's entirely possible to use standard HTML elements and forms with Blazor, but using the built-in controls make certain things a lot easier (validation being one, as we'll see in a moment).

@bind-Value takes care of both reading the value from a field and updating it when a user types in a new value.

The form itself is based on a model of our choosing (where the form values will live) and we've told it which method to invoke when the form is submitted (and is valid).

Note how we've included a DataAnnotationsValidator and ValidationSummary; these components wire up the form to automatically respect any validation rules we set up on our model.

Here's the rest of the code:

@code {
    protected ContactUsModel FormModel { get; set; } = new ContactUsModel();

    async Task HandleValidSubmit()
    {
        // post to your API
        Console.WriteLine($"{FormModel.Name}: {FormModel.Comments}");
    }

    protected class ContactUsModel
    {
        [Required]
        public string Name { get; set; }
        public string Comments { get; set; }
    }
}

The ContactUsModel class could live anywhere in our project.

We have a FormModel property and HandleValidSubmit method.

When someone fills in the form, if they've met the validation rules (a Name has been entered) then HandleValidSubmit will be invoked.

Otherwise, the ValidationSummary will be used to show which fields have validation errors.

Blazor example of form with validation errors

Routing in React

As with forms, React (deliberately) doesn't ship with any built-in routing engine, leaving you free to choose which of various third-party routing options you want to use.

Notable options include "Reach Router" and "React Router."

Routing in Blazor

Blazor does include routing and leans on ASP.NET's existing routing engine. If you want to make a component "routable" you can add a directive:

@page "/GreetMe"

<h1>
    Welcome!
</h1>

Now any request to http://<your-web-site-here>/GreetMe will render this component.

You can also pass data in via the route, like this:

@page "/GreetMe/{Name}"

<h1>
    Welcome @Name!
</h1>

@code {
    [Parameter]
    public string Name { get; set; }
}

Now any request to http://<your-web-site-here>/GreetMe/Jon will render a more personalized greeting (well, if your name's Jon according to this example!)

Fetching Data from an API Using React

React leaves data fetching to your discretion.

You can use the native fetch API:

let response = await fetch(url);
if(response.ok){
    let json = await response.json;
}

Or you can employ one of many third-party libraries, such as "Axios" which offers more features and flexibility.

Typically you'd fetch data using something like React's useEffect hook, then update your component state with the results.

useEffect(()=> {    
    async loadData(){
        let response = await fetch('api/Tickets');
        setTickets(await response.json);
    }
    loadData();    
}, []);

UseEffect hooks are intended to allow your component to handle "side effects", in this case, fetching data.

From here you'd typically use JavaScript's map function to loop over your data and render something for each item.

{tickets.map(ticket=> <div>
    {ticket.Title}
</div>}

Fetching Data from an API Using Blazor

With Blazor, you can use HttpClient for all your data-fetching needs!

Under the hood, this defers to the native fetch API, but you can generally just ignore that and use the abstraction.

Here's an example:

@using System.Net.Http
@inject HttpClient Http

@foreach(var ticket in _tickets){
    <div>
        @ticket.Title
    </div>  
}

@code {
    private Tickets[] _tickets;

    protected override async Task OnInitializedAsync(){
        _tickets = await Http.GetFromJsonAsync<TicketSummary>("api/Tickets");
    }
}

Shared Models—Blazor's Super Power?

Blazor has a useful trick up its sleeve (compared to React, or any other existing frontend framework) when it comes to the interaction between your UI and API.

Because you're writing your web app in C#, you can use the same data models in your frontend and backend (API) code.

Let's say for example you need to retrieve a list of people...

Diagram showing how it works using one shared model between client and API with Blazor

The Person model lives in a shared class library.

Both your Web API and Blazor Client projects reference this shared library.

Now your API can be strongly typed, returning (and accepting) data using the Person model.

The data is still serialized and sent "over the wire" as JSON data, but your Blazor application can deserialize the JSON data using the exact same Person model that was used to serialize it in the first place.

Now if you make breaking changes to your Person model, you'll immediately see compilation errors if you've introduced any breaking changes which affect either the client or server use of the model.

Pros and Cons

So all this brings us to the obvious question: which one should you choose?

Well, naturally, this will depend on your own background, skills and preferences.

We've seen how there are a number of similarities between the two, but also a few key differences.

React

React is generally unopinionated when it comes to the things that aren't its primary focus, like routing, form handling, etc.

This could count as either a pro or a con depending on your perspective.

Also, if you're writing React components, you have little choice but to use JavaScript, and adopt everything that comes along for the ride (the ecosystem, npm, bundling, etc.).

Create React App offers a handy abstraction for some of the thornier details (like webpack), so you can focus on building your application.

React Pros

  • Well-established and battle-tested component model
  • "Create React App" simplifies the JS build process
  • "Light Touch" library—no "extras" like routers included (freedom to choose your own path!)
  • JSX—keeps the parts of the UI that change together in one place
  • Freedom to plug in other JS libraries
  • A large existing JS library ecosystem to lean on
  • Can be deployed as static files to hosting like Netlify, GitHub Pages, etc.

React Cons

  • JSX (if you don't like it!)
  • No built-in router, form handling, etc. (can slow things down as you have to plug the gaps with packages or your own solutions)
  • JavaScript (again, if you don't like it)
    • You have to use it
    • You have to learn it (difficult and time-consuming if you spend most of your time using another language like C#)
  • The JS "ecosystem" can get complicated (build tools, package managers, compilation for different browsers, etc.)

Blazor

Blazor offers a few advantages, especially if you're coming from a C# background.

You can bring your existing C# skills, experience and knowledge to the modern web application party!

You can stick to the ecosystem you already know (NuGet, the dotnet tooling, Visual Studio or VS Code).

Shared models between client and backend API. This is a big deal and makes it much harder to inadvertently break your application.

Routing, form handling and validation are baked in.

You can still call out to JavaScript if you need to.

Blazor Pros

  • Write modern web apps using C#
  • Built-in form handling, validation, routing and data-fetching client
  • Ability to bring in third-party code via NuGet packages
  • You can use the tools you already know (Visual Studio, VS Code, Rider, etc.)
  • Can be deployed as static files
  • Shared models significantly reduce the chances of accidentally breaking the client
  • You can use the same component model in the browser (using WebAssembly) or on the server (using Blazor Server)
  • Support to use the same Blazor component model on windows and for mobile development is coming

Blazor Cons

  • New framework, will take time to bed in and gain adoption
  • Sizeable initial download of .NET framework to browser on first load
  • Tooling also young and will evolve over time
  • Fewer resources available on the internet (tutorials, etc.) compared to React
  • Does depend on WebAssembly support in the browser (although this is now widely supported)

One thing to call out here is the initial download time.

When someone accesses your Blazor Wasm app for the first time, their browser will download a version of the .NET framework as well as your application's files.

Once they have these files they don't need to download them again, but it does mean you're likely to see a "loading..." indicator first time round.

The team at Microsoft has done a lot of work to get this initial download size down, but naturally this means Blazor is better suited to some web applications than others.

You probably wouldn't want to use it for things like product landing pages where there's little to no business logic and it's imperative that the page loads as quickly as possible.

But, for any Line of Business apps this initial download is unlikely to pose a major issue.

Over to You!

So, are you thinking of using Blazor? Is it a contender or your next project, or will you be sticking with React for now?

The choice is yours!


Jon Hilton
About the Author

Jon Hilton

Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.

Related Posts

Comments

Comments are disabled in preview mode.