I've Been Writing C# for Years. Here's What Nobody Told Me About React.

javascript dev.to

_When my team said we were adding a React frontend to our .NET API, I did what any self-respecting senior developer does.

I Googled it. Watched a YouTube tutorial for 20 minutes. Closed the tab. Opened a new tab. Googled "React for C# developers." Got results that assumed I was either a complete beginner who didn't know what a variable was, or a JavaScript wizard who already knew what a closure was._

I was neither. I was a C# developer. I knew exactly what I was doing — just not in this language.

So I figured it out the hard way. And now I'm going to save you the weeks I wasted by mapping every React concept directly to what you already know in C#.

No "what is a variable" explanations. No JavaScript history lessons. Just: here's what you know, here's what it's called in React, here's why it works the same way.


First, the mindset shift

In C#, the server does the work. Your controller fetches data, builds a model, passes it to a Razor view, and the browser gets back finished HTML. The browser is basically a dumb terminal receiving rendered output.

React flips this completely. You ship JavaScript to the browser, and the browser builds the UI itself. Your .NET API becomes just a data supplier — it hands over JSON and React figures out the rest.

C#/.NET world: Browser → Server → Server renders HTML → Browser displays it
React world: Browser → Server → Server returns JSON → Browser renders it

That's the whole paradigm shift. Everything else is just syntax.


Variables: You already know this

This one's genuinely easy. JavaScript has three ways to declare variables and they map almost perfectly to what you already know.

JavaScript C# Equivalent When to use
const name = "John" readonly string name = "John" Value won't change — use this by default
let count = 0 int count = 0 Value will change (loop counters, etc.)
var name = "John" var name = "John" Old style — avoid in modern JS

The mental model: use const for everything until you need to reassign it, then use let. Forget var exists. Seriously.

// JavaScript
const apiUrl = "https://api.myapp.com";   // won't change
let currentPage = 1;                       // will change as user navigates

// Compare to C#
readonly string apiUrl = "https://api.myapp.com";
int currentPage = 1;
Enter fullscreen mode Exit fullscreen mode

Arrow Functions: You've already been writing these

Here's something that'll make you smile. You've been writing arrow functions in C# for years. You just called them lambdas.

// C# lambda
var double = (int x) => x * 2;
double(5); // returns 10
Enter fullscreen mode Exit fullscreen mode
// JavaScript arrow function — almost identical!
const double = (x) => x * 2;
double(5); // returns 10
Enter fullscreen mode Exit fullscreen mode

The syntax is nearly the same. The behavior is nearly the same. The only real difference is types — JavaScript doesn't have them (unless you use TypeScript, which is a conversation for another day).

Multi-line works the same way too:

// Single line — implicit return, no braces needed
const add = (a, b) => a + b;

// Multi-line — use braces, explicit return required
const getTotal = (transactions) => {
    const sum = transactions.reduce((acc, t) => acc + t.amount, 0);
    return sum;
};
Enter fullscreen mode Exit fullscreen mode

Array Methods: LINQ with different names

This is where C# developers get an instant superpower in JavaScript. You already know how to think in LINQ. JavaScript's array methods are the same operations with different names.

JavaScript LINQ Equivalent What it does
.map(fn) .Select(fn) Transforms each item — returns new array
.filter(fn) .Where(fn) Returns items matching a condition
.reduce(fn, init) .Aggregate(init, fn) Reduces to a single value (totals, sums)
.find(fn) .FirstOrDefault(fn) Returns first match or undefined
.some(fn) .Any(fn) Returns true if any item matches
.every(fn) .All(fn) Returns true if all items match

👉 React for C#/.NET Developers — Complete Training Guide

// C# LINQ
var amounts = transactions.Select(t => t.Amount).ToList();
var deposits = transactions.Where(t => t.Type == "Deposit").ToList();
var total = transactions.Aggregate(0m, (sum, t) => sum + t.Amount);
Enter fullscreen mode Exit fullscreen mode
// JavaScript — same logic, different syntax
const amounts = transactions.map(t => t.amount);
const deposits = transactions.filter(t => t.type === "Deposit");
const total = transactions.reduce((sum, t) => sum + t.amount, 0);
Enter fullscreen mode Exit fullscreen mode

Why this matters for React: .map() is how you render lists. Instead of returning transformed values, you return JSX elements. This single pattern is responsible for roughly 40% of the React code you'll ever write.

// Rendering a list in React — it's just .map() returning JSX
return (
    <ul>
        {transactions.map(t => (
            <li key={t.id}>{t.accountNumber}: ${t.amount}</li>
        ))}
    </ul>
);
Enter fullscreen mode Exit fullscreen mode

Components: Partial Views with superpowers

A React component is a JavaScript function that returns HTML-like syntax (called JSX). Think of it as a Razor Partial View that also contains its own controller logic.

// C# — ViewComponent (closest equivalent)
public class TransactionCardViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(string accountNumber, decimal amount, string type)
    {
        return View(new TransactionCardViewModel { ... });
    }
}
Enter fullscreen mode Exit fullscreen mode
// React — same idea, dramatically less ceremony
function TransactionCard({ accountNumber, amount, type }) {
    const color = type === "Deposit" ? "green" : "red";

    return (
        <div>
            <h3>{accountNumber}</h3>
            <p style={{ color }}>{amount.toLocaleString()}</p>
        </div>
    );
}

// Use it like a custom HTML tag anywhere in your app
<TransactionCard accountNumber="ACC-001" amount={5000} type="Deposit" />
Enter fullscreen mode Exit fullscreen mode

The JSX syntax looks weird at first — HTML inside JavaScript? — but you get used to it within a day. The key rules:

  • class becomes className (because class is a reserved word in JavaScript)
  • for becomes htmlFor on labels
  • Every tag must be closed — <br /> not <br>
  • Curly braces {} embed JavaScript expressions, exactly like @() in Razor

Props: Method parameters, but for components

Props are how you pass data into a component. They work exactly like parameters to a C# method — the caller provides values, the component uses them, and they're read-only inside the component.

// C# method — you pass parameters
public string FormatTransaction(string accountNumber, decimal amount)
{
    return $"{accountNumber}: ₱{amount:N0}";
}
Enter fullscreen mode Exit fullscreen mode
// React component — you pass props (same idea)
function TransactionRow({ accountNumber, amount }) {
    return <p>{accountNumber}: {amount.toLocaleString()}</p>;
}

// Calling it — like calling a method, but in JSX
<TransactionRow accountNumber="ACC-001" amount={5000} />
Enter fullscreen mode Exit fullscreen mode

The destructuring syntax { accountNumber, amount } in the function parameter is just JavaScript's way of pulling named properties out of an object. It's sugar for props.accountNumber, props.amount.


State: The concept that makes React click

This is where React gets genuinely different from anything in server-side C# — and once it clicks, everything makes sense.

State is data that belongs to a component and can change over time. When state changes, React automatically re-renders the component with the new value. No manual DOM manipulation, no page reload. The UI just updates.

import { useState } from "react";

function TransactionFilter() {
    // const [currentValue, setterFunction] = useState(initialValue)
    const [filter, setFilter] = useState("All");

    return (
        <div>
            <button onClick={() => setFilter("All")}>All</button>
            <button onClick={() => setFilter("Deposit")}>Deposits</button>
            <button onClick={() => setFilter("Withdraw")}>Withdrawals</button>

            <p>Currently showing: {filter}</p>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

The closest C# mental model: think of useState like INotifyPropertyChanged — when the value changes, something automatically updates in response. Except React handles the "update the UI" part for you automatically.

The golden rule that trips up every C# developer at least once:

// WRONG — this does nothing to the UI
filter = "Deposit";

// CORRECT — this triggers a re-render
setFilter("Deposit");
Enter fullscreen mode Exit fullscreen mode

React only knows to re-render the component when you use the setter function. Direct assignment is invisible to React. Burn this into your brain on day one.


useEffect: The Form_Load event you always wanted

useEffect is how you run code at specific moments in a component's life — most commonly to fetch data when the component first appears on screen.

import { useState, useEffect } from "react";

function TransactionList() {
    const [transactions, setTransactions] = useState([]);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
        // This runs once when the component first renders
        fetch("/api/transactions", {
            headers: { Authorization: `Bearer ${localStorage.getItem("jwt_token")}` }
        })
        .then(r => r.json())
        .then(data => {
            setTransactions(data);
            setIsLoading(false);
        });
    }, []); // ← the empty [] means "run once on mount"

    if (isLoading) return <p>Loading...</p>;

    return (
        <ul>
            {transactions.map(t => <li key={t.id}>{t.accountNumber}</li>)}
        </ul>
    );
}
Enter fullscreen mode Exit fullscreen mode

The second argument to useEffect is the dependency array:

Syntax When it runs C# equivalent
useEffect(() => {}, []) Once on mount Form_Load
useEffect(() => {}, [id]) On mount + every time id changes INotifyPropertyChanged
useEffect(() => {}) After every render No direct equivalent

Context: Dependency Injection without the ceremony

If you've ever needed to pass a value (like a JWT token or the current user) through five levels of components, you've hit "prop drilling" — passing props down through components that don't even need them just to get them to a component that does.

Context solves this exactly the way DI solves it in C# — register it once, consume it anywhere.

// 1. Create the context (like registering in DI container)
const AuthContext = createContext(null);

export function AuthProvider({ children }) {
    const [token, setToken] = useState(localStorage.getItem("jwt_token"));

    return (
        <AuthContext.Provider value={{ token, setToken }}>
            {children}
        </AuthContext.Provider>
    );
}

// 2. Wrap your app — once
<AuthProvider>
    <App />
</AuthProvider>

// 3. Consume anywhere — no prop drilling needed
function NavBar() {
    const { token } = useContext(AuthContext);
    return <p>Logged in: {token ? "Yes" : "No"}</p>;
}
Enter fullscreen mode Exit fullscreen mode
// C# equivalent mindset
// 1. Register in DI: services.AddScoped<IAuthService, AuthService>();
// 2. Inject anywhere: public NavController(IAuthService auth) { }
Enter fullscreen mode Exit fullscreen mode

Same problem, same solution, different syntax.


The one thing that takes the longest to unlearn

Coming from C#, your instinct is to think about when things happen. You're used to sequential execution — line 1 runs, then line 2, then line 3.

React is declarative. You describe what the UI should look like given the current state, and React figures out when and how to make it happen. You stop thinking "do this when the user clicks" and start thinking "the UI looks like X when the filter is Y."

This is the shift that takes a few days to internalize. Be patient with yourself on this one. It's not a syntax problem — it's a mindset problem. And once it clicks, you'll wonder how you ever thought the other way.


Where to go from here

If this mapped well to how you think, I put together a complete 8-hour React training guide built specifically for C#/.NET developers — every concept extended further than this article, with hands-on exercises, full solutions, and a complete SecureBank dashboard build using JWT auth, AG Grid, and Recharts.

👉 React for C#/.NET Developers — Complete Training Guide

It's the guide I wish existed when I started. Hope it saves you the weeks it took me to figure this out.


Got questions? Drop them in the comments — especially if something here didn't map the way you expected. Those are usually the most interesting discussions.

Read Full Tutorial open_in_new
arrow_back Back to Tutorials