Nghia Nguyen Portfolio
Let's Talk

Learn Blazor from React in Z Minutes

Inspired by the concept of Learn X in Y Minutes, I put together this handy guide to help React developers quickly get up to speed with Blazor. Who knows, maybe this could be the start of a whole "Learn X from Y in Z Minutes" series! 😉 Sure, reading documentation is quicker than watching hours of tutorials, but comparing a new framework to one you already know? That's next-level speed-learning!

This article presents React and Blazor syntax side by side, assuming you're already proficient in React. By highlighting the similarities and differences, you'll be able to leverage your existing knowledge to quickly understand Blazor's approach.

Component Structure

React


function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Blazor


@page "/welcome"
<h1>Hello, @Name</h1>

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

State Management

React


import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <>
      <p>Current count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </>
  );
}

Blazor


<button @onclick="IncrementCount">Click me</button>

<p>Current count: @count</p>

@code {
    private int count = 0;

    private void IncrementCount()
    {
        count++;
    }
}

Conditional Rendering

React


function Greeting({ isLoggedIn }) {
  return isLoggedIn ? <UserGreeting /> : <GuestGreeting />;
}

Blazor


@if (IsLoggedIn)
{
    <UserGreeting />
}
else
{
    <GuestGreeting />
}

@code {
    [Parameter]
    public bool IsLoggedIn { get; set; }
}

List Rendering

React


function NumberList({ numbers }) {
  return (
    <ul>
      {numbers.map((number) => (
        <li key={number}>{number}</li>
      ))}
    </ul>
  );
}

Blazor


<ul>
    @foreach (var number in Numbers)
    {
        <li>@number</li>
    }
</ul>

@code {
    [Parameter]
    public List<int> Numbers { get; set; }
}

Event Handling

React


function ActionLink() {
  const handleClick = () => {
    console.log('The link was clicked.');
  };

  return (
    <a href="#" onClick={handleClick}>Click me</a>
  );
}

Blazor


<a href="#" @onclick="HandleClick">Click me</a>

@code {
    private void HandleClick()
    {
        Console.WriteLine("The link was clicked.");
    }
}

Event Interaction

React


function ClickCounter() {
  const [count, setCount] = useState(0);

  const handleClick = (e) => {
    setCount(count + 1);
    console.log('Button clicked!');
    console.log('Event type:', e.type);
    console.log('Mouse position:', e.clientX, e.clientY);
  };

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

Blazor


@page "/"

<button @onclick="HandleClick">
    Clicked @clickCount times
</button>

@code {
    private int clickCount = 0;

    private void HandleClick(MouseEventArgs e)
    {

        clickCount++;
        Console.WriteLine("Button clicked!");
        Console.WriteLine($"Event type: {e.Type}");
        Console.WriteLine($"Mouse position: {e.ClientX}, {e.ClientY}");
    }
}

Event Modifiers

React


    function EventModifiers() {
      const handleClick = (e) => {
        console.log('Button clicked');
      };
    
      const handleKeyPress = (e) => {
        if (e.key === 'Enter') {
          console.log('Enter key pressed');
        }
      };
    
      return (
        <div>
          <button onClick={(e) => {
            e.stopPropagation();
            handleClick(e);
          }}>
            Click me (stop propagation)
          </button>
          <input
            type="text"
            onKeyPress={(e) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                handleKeyPress(e);
              }
            }}
            placeholder="Press Enter"
          />
        </div>
      );
    }

Blazor


    <div>
        <button @onclick="HandleClick" @onclick:stopPropagation>
            Click me (stop propagation)
        </button>
        <input @onkeypress="HandleKeyPress" 
               @onkeypress:preventDefault="IsEnterKey"
               placeholder="Press Enter" />
    </div>
    
    @code {
        private void HandleClick()
        {
            Console.WriteLine("Button clicked");
        }
    
        private void HandleKeyPress(KeyboardEventArgs e)
        {
            if (e.Key == "Enter")
            {
                Console.WriteLine("Enter key pressed");
            }
        }
    
        private bool IsEnterKey(KeyboardEventArgs e) => e.Key == "Enter";
    }

Forms and Two-Way Binding

React


import { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  const handleSubmit = (event) => {
    alert(`Name submitted: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

Blazor


<EditForm Model="@formModel" OnValidSubmit="@HandleValidSubmit">
    <InputText @bind-Value="formModel.Name" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    private FormModel formModel = new FormModel();

    private void HandleValidSubmit()
    {
        Console.WriteLine($"Name submitted: {formModel.Name}");
    }
}

public class FormModel
{
    public string Name { get; set; }
}

Lifecycle Methods

React


    // React Functional Component with Hooks
    import React, { useState, useEffect, useLayoutEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // Similar to componentDidMount and componentDidUpdate
      useEffect(() => {
        console.log('Component mounted or updated');
        
        // Cleanup: Similar to componentWillUnmount
        return () => {
          console.log('Component will unmount');
        };
      });
    
      // Only runs after the first render (componentDidMount)
      useEffect(() => {
        console.log('Component mounted');
      }, []);
    
      // Triggered when count changes (componentDidUpdate with specific dependency)
      useEffect(() => {
        console.log('Count changed:', count);
      }, [count]);
    
      // Runs before browser paints (similar to componentWillUpdate)
      useLayoutEffect(() => {
        console.log('Layout effect (before paint)');
      });
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
    
    // React Error Boundary (Class Component)
    class ErrorBoundary extends React.Component {
      componentDidCatch(error, errorInfo) {
        console.log('Error caught:', error, errorInfo);
      }
    
      render() {
        return this.props.children;
      }
    }

Blazor


    // Blazor Component Lifecycle
    @page "/"
    @implements IDisposable
    
    <div>
        <p>Count: @count</p>
        <button @onclick="IncrementCount">Increment</button>
    </div>
    
    @code {
        private int count = 0;
    
        // Similar to componentDidMount (runs once when component is initialized)
        protected override void OnInitialized()
        {
            Console.WriteLine("Component initialized");
        }
    
        // Similar to componentWillUpdate (before rendering after parameter changes)
        protected override void OnParametersSet()
        {
            Console.WriteLine("Parameters set");
        }
    
        // Similar to componentDidUpdate (runs after each render, can check if it's the first render)
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                Console.WriteLine("Component mounted");
            }
            Console.WriteLine("Component rendered");
        }
    
        private void IncrementCount()
        {
            count++;
            Console.WriteLine($"Count changed: {count}");
        }
    
        // Cleanup: Similar to componentWillUnmount (disposal of component)
        public void Dispose()
        {
            Console.WriteLine("Component will be disposed");
        }
    
        // Blazor doesn't have direct error boundaries, but error handling can be done
        public void HandleError(Exception exception)
        {
            Console.WriteLine($"Error caught: {exception.Message}");
        }
    }

Context (React) vs. Dependency Injection (Blazor)

React


import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <Button theme={theme}>Click me</Button>;
}

Blazor


// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ThemeService>();
}

// ThemeService.cs
public class ThemeService
{
    public string CurrentTheme { get; set; } = "light";
}

// ThemedButton.razor
@inject ThemeService ThemeService

<button>@ThemeService.CurrentTheme</button>

@code {
    // ThemeService is injected automatically
}

Routing

React


    // Using nextjs router
    // app/page.js
    import Link from 'next/link';
    
    export default function Home() {
      return (
        <div>
          <nav>
            <ul>
              <li><Link href="/">Home</Link></li>
              <li><Link href="/about">About</Link></li>
            </ul>
          </nav>
          <h1>Home Page</h1>
        </div>
      );
    }
    
    // app/about/page.js
    export default function About() {
      return <h1>About Page</h1>;
    }
    

Blazor


    // NavMenu.razor
    <ul>
        <li><NavLink href="/">Home</NavLink></li>
        <li><NavLink href="/about">About</NavLink></li>
    </ul>
    
    // Pages/Index.razor
    @page "/"
    <h1>Home Page</h1>
    
    // Pages/About.razor
    @page "/about"
    <h1>About Page</h1>
    

As you can see, while the syntax differs, the core concepts remain similar. Blazor uses a more C#-centric approach, which might feel familiar if you have experience with backend development.

Remember, this is just a starting point. As you delve deeper into Blazor, you'll discover its unique features and how it leverages the power of .NET in web development.

By comparing React and Blazor side by side, you can quickly grasp the fundamental differences and similarities. This approach allows you to transfer your React knowledge to Blazor, accelerating your learning process and helping you become productive in this new framework faster.

Please note that this comparison may not be complete. I'm continuously adding more examples and insights as I explore and learn more about both React and Blazor. Stay tuned for updates!