LEARN BUILD GROW TOGETHER
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /Frontend Development /React: Complete Developer Learning Guide

React: Complete Developer Learning Guide

React · Beginner to Advanced

React: Complete Developer Learning Guide

Learn React from JavaScript and JSX fundamentals to components, Hooks, state management, routing, forms, API integration, authentication, testing, performance, and integration with ASP.NET Core Web API.

Introduction

React is an open-source JavaScript library used to build interactive user interfaces. It was originally developed by Facebook and is now maintained by Meta and the React community.

React helps developers create reusable UI components and manage how application interfaces respond to changing data. It is commonly used to build single-page applications, dashboards, administrative portals, e-commerce applications, and other interactive web experiences.

Unlike Angular, which provides a broader application framework, React focuses primarily on the UI layer. Developers commonly combine React with additional libraries for routing, forms, data fetching, and application state management.

For a .NET full-stack developer, React can serve as the frontend layer while ASP.NET Core Web API handles backend operations, business logic, authentication, and database communication.

Important: React is a UI library, not a complete backend framework. React itself does not replace ASP.NET Core Web API, SQL Server, or server-side business logic.

1. React Fundamentals

What is React?

React is a JavaScript library for building user interfaces. It allows developers to divide an application into independent, reusable components.

Each component describes a portion of the UI. React uses component state and props to determine what should appear on the screen and updates the rendered interface when relevant data changes.

What is a Single-Page Application?

A Single-Page Application (SPA) loads an application shell and updates its content as users navigate and interact with the application. In many cases, navigation does not require a complete browser page reload.

For example, an employee management application can have dashboard, employee, department, and reporting screens. A routing library can switch between these views while the application remains loaded.

Important React characteristics

  • Component-based architecture: Build interfaces using reusable components.
  • JSX: Write UI markup inside JavaScript or TypeScript.
  • Declarative UI: Describe what the interface should display for the current state.
  • Props: Pass data from parent components to child components.
  • State: Store information that can change over time.
  • Hooks: Use state and other React capabilities in function components.
  • One-way data flow: Data generally flows from parent components to children through props.
  • Reusable logic: Extract shared behavior into custom Hooks.

React vs Angular

ReactAngular
JavaScript library focused on UI development.Full frontend application framework.
Uses JSX for UI markup.Uses HTML-based templates.
Primarily uses function components and Hooks in modern applications.Uses components, services, and dependency injection.
Uses additional libraries for routing and other application capabilities.Provides built-in routing and many application features.
Uses JavaScript or TypeScript.Primarily uses TypeScript.
Uses state and props for UI data flow.Uses component properties, signals, services, and observables.

React vs React Native

React is used to build web user interfaces. React Native uses React concepts to build native mobile applications for platforms such as Android and iOS.

ReactReact Native
Builds web applications.Builds native mobile applications.
Uses HTML elements such as div and button.Uses native components such as View and Text.
Runs in a web browser.Runs through native mobile application infrastructure.

2. JavaScript Fundamentals

JavaScript is the programming language used by React. Before learning React, developers should understand variables, functions, objects, arrays, modules, promises, and modern ES6+ syntax.

Variables

let employeeName = 'Rahul';
const employeeAge = 30;

let isActive = true;

console.log(employeeName);
console.log(employeeAge);

Use const when a variable binding should not be reassigned and let when reassignment is required.

Arrow functions

const calculateTotal = (price, quantity) => {
    return price * quantity;
};

const total = calculateTotal(500, 3);

console.log(total);

Objects

const employee = {
    employeeId: 1,
    employeeName: 'Rahul',
    email: 'rahul@example.com',
    salary: 50000
};

console.log(employee.employeeName);

Arrays and map()

The map() method creates a new array by transforming each element. It is frequently used to render lists in React.

const employees = [
    { id: 1, name: 'Rahul' },
    { id: 2, name: 'Amit' },
    { id: 3, name: 'Neha' }
];

const employeeNames = employees.map(
    employee => employee.name
);

console.log(employeeNames);

Destructuring

const employee = {
    employeeName: 'Rahul',
    salary: 50000
};

const { employeeName, salary } = employee;

console.log(employeeName);
console.log(salary);

Spread operator

The spread operator is frequently used to create updated copies of arrays and objects.

const employee = {
    employeeId: 1,
    employeeName: 'Rahul'
};

const updatedEmployee = {
    ...employee,
    salary: 50000
};

const numbers = [10, 20, 30];

const updatedNumbers = [...numbers, 40];

Promises

A Promise represents the eventual completion or failure of an asynchronous operation.

const employeePromise = new Promise((resolve, reject) => {
    resolve('Employee data loaded');
});

employeePromise.then(result => {
    console.log(result);
});

Async and await

async function getEmployee() {
    try {
        const response = await fetch('/api/employees/1');

        if (!response.ok) {
            throw new Error('Request failed');
        }

        const employee = await response.json();

        console.log(employee);
    } catch (error) {
        console.error(error);
    }
}

JavaScript modules

Modules allow code to be divided into separate files and reused through imports and exports.

// employee.js

export const employeeName = 'Rahul';

export function getEmployeeName() {
    return employeeName;
}
// app.js

import {
    employeeName,
    getEmployeeName
} from './employee.js';

console.log(employeeName);
console.log(getEmployeeName());

3. TypeScript with React

TypeScript adds static typing to JavaScript. React supports TypeScript through JSX-compatible TypeScript files and React type definitions.

For .NET developers, TypeScript may feel familiar because it supports interfaces, classes, generics, access modifiers, and explicit type definitions.

Create a TypeScript type

interface Employee {
    employeeId: number;
    employeeName: string;
    email: string;
    salary: number;
    isActive: boolean;
}

const employee: Employee = {
    employeeId: 1,
    employeeName: 'Rahul',
    email: 'rahul@example.com',
    salary: 50000,
    isActive: true
};

TypeScript component file

React components written with TypeScript and JSX commonly use the .tsx extension.

interface EmployeeProps {
    employeeName: string;
    salary: number;
}

function EmployeeCard({
    employeeName,
    salary
}: EmployeeProps) {
    return (
        <div>
            <h3>{employeeName}</h3>
            <p>Salary: {salary}</p>
        </div>
    );
}

Type vs interface

TypeInterface
Can define unions, intersections, primitives, tuples, and object shapes.Primarily describes object shapes and contracts.
Supports composition through intersections.Supports extension and declaration merging.
Useful for flexible type expressions.Useful for object contracts and component props.

Optional properties

interface Employee {
    employeeId: number;
    employeeName: string;
    email?: string;
}

Union types

type EmployeeStatus =
    | 'Active'
    | 'Inactive'
    | 'OnLeave';

let status: EmployeeStatus = 'Active';

Typing component props

interface ButtonProps {
    title: string;
    disabled?: boolean;
    onClick: () => void;
}

function CustomButton({
    title,
    disabled = false,
    onClick
}: ButtonProps) {
    return (
        <button
            disabled={disabled}
            onClick={onClick}>
            {title}
        </button>
    );
}

Typing useState

import { useState } from 'react';

interface Employee {
    employeeId: number;
    employeeName: string;
}

function EmployeeComponent() {
    const [employee, setEmployee] =
        useState<Employee | null>(null);

    return (
        <div>
            {employee?.employeeName}
        </div>
    );
}

Generics

function getFirstItem<T>(items: T[]): T | undefined {
    return items[0];
}

const firstNumber =
    getFirstItem<number>([10, 20, 30]);

const firstName =
    getFirstItem<string>(['Rahul', 'Amit']);
TypeScript improves development-time type checking, but TypeScript types and interfaces do not validate API JSON at runtime. Use runtime validation when data from external sources must be verified.

4. Development Environment and React Setup

Prerequisites

  • Node.js: JavaScript runtime required by development tools.
  • npm: Package manager for installing dependencies.
  • Visual Studio Code: Common editor for React and TypeScript.
  • Browser: Chrome, Edge, Firefox, or another modern browser.
  • React Developer Tools: Browser extension for inspecting React components.

Check installed versions

node --version
npm --version

Create a React application using Vite

Vite is a modern frontend build tool commonly used to create client-rendered React applications.

npm create vite@latest employee-management -- --template react-ts

Navigate into the project:

cd employee-management

Install dependencies:

npm install

Run the development server:

npm run dev

Open the local address displayed by Vite, commonly http://localhost:5173.

Install React Router

npm install react-router

This guide uses the current React Router package style. Existing applications may use different versions and setup conventions.

Important npm commands

CommandPurpose
npm create vite@latestCreates a Vite project.
npm installInstalls project dependencies.
npm run devStarts the development server.
npm run buildCreates a production build.
npm run previewPreviews the production build locally.
npm install package-nameInstalls a dependency.
npm uninstall package-nameRemoves a dependency.

React project structure

employee-management/
│
├── public/
│
├── src/
│   ├── assets/
│   │
│   ├── components/
│   │   ├── common/
│   │   └── layout/
│   │
│   ├── features/
│   │   ├── employees/
│   │   │   ├── components/
│   │   │   ├── services/
│   │   │   └── models/
│   │   │
│   │   └── departments/
│   │       ├── components/
│   │       ├── services/
│   │       └── models/
│   │
│   ├── hooks/
│   ├── context/
│   ├── services/
│   ├── types/
│   ├── App.tsx
│   └── main.tsx
│
├── package.json
├── tsconfig.json
└── vite.config.ts

This is an example organization. React does not enforce a specific folder structure.

5. JSX and Rendering

What is JSX?

JSX stands for JavaScript XML. It is a syntax extension that allows developers to write HTML-like markup inside JavaScript or TypeScript code.

React uses JSX to describe the UI returned by components.

function Welcome() {
    return (
        <h1>Welcome to React</h1>
    );
}

export default Welcome;

JSX rules

  • Return one parent element or use a React Fragment.
  • Close all JSX elements.
  • Use className instead of HTML's class attribute.
  • Use camelCase for most DOM event properties.
  • Use curly braces to insert JavaScript expressions.
  • Component names must start with an uppercase letter.

JavaScript expressions in JSX

function Employee() {
    const employeeName = 'Rahul';

    return (
        <div>
            <h2>Employee Details</h2>
            <p>Name: {employeeName}</p>
            <p>Total: {500 * 2}</p>
        </div>
    );
}

React Fragment

A Fragment groups multiple elements without introducing an additional DOM element.

function EmployeeDetails() {
    return (
        <>
            <h2>Employee Details</h2>
            <p>Employee information</p>
        </>
    );
}

Rendering lists

The JavaScript map() method is commonly used to render arrays of data.

const employees = [
    { id: 1, name: 'Rahul' },
    { id: 2, name: 'Amit' },
    { id: 3, name: 'Neha' }
];

function EmployeeList() {
    return (
        <ul>
            {employees.map(employee => (
                <li key={employee.id}>
                    {employee.name}
                </li>
            ))}
        </ul>
    );
}

The key property helps React identify list items when items are inserted, removed, or reordered. Prefer stable unique identifiers from the data.

Conditional rendering

function LoginStatus() {
    const isLoggedIn = true;

    return (
        <div>
            {isLoggedIn
                ? <p>Welcome back!</p>
                : <p>Please log in.</p>
            }
        </div>
    );
}

Conditional rendering can also use logical AND:

{isAdmin && <button>Manage Users</button>}

Styling in React

React supports different styling approaches. Developers can use regular CSS files, CSS Modules, inline styles, or styling libraries.

CSS class example:

function EmployeeCard() {
    return (
        <div className="employee-card">
            Employee Details
        </div>
    );
}
/* employee.css */

.employee-card {
    padding: 16px;
    border: 1px solid #ddd;
    border-radius: 6px;
}

Inline style example:

<h2 style={{ color: 'blue', fontSize: '22px' }}>
    Employee Details
</h2>

6. Components and Props

What is a component?

A component is a reusable piece of the user interface. It can represent a small button, a form, a table, a navigation menu, or an entire page.

Modern React applications primarily use function components.

Create a function component

function Employee() {
    return (
        <div>
            <h2>Employee Information</h2>
            <p>Name: Rahul</p>
        </div>
    );
}

export default Employee;

Use a component inside another component

function App() {
    return (
        <div>
            <h1>Employee Management</h1>
            <Employee />
        </div>
    );
}

What are props?

Props are inputs passed from a parent component to a child component. They allow components to be reusable with different data.

interface EmployeeProps {
    employeeName: string;
    salary: number;
}

function EmployeeCard({
    employeeName,
    salary
}: EmployeeProps) {
    return (
        <div>
            <h3>{employeeName}</h3>
            <p>Salary: {salary}</p>
        </div>
    );
}

Parent component:

function App() {
    return (
        <div>
            <EmployeeCard
                employeeName="Rahul"
                salary={50000}
            />

            <EmployeeCard
                employeeName="Amit"
                salary={60000}
            />
        </div>
    );
}

Props vs state

PropsState
Passed from a parent component.Managed by a component or a shared state mechanism.
Read-only from the receiving component's perspective.Updated through the relevant state update mechanism.
Used to configure child components.Used to represent changing application data.
Supports parent-to-child data flow.Controls how the UI responds to changes.

Passing children

The special children prop allows a component to receive content placed between its opening and closing JSX tags.

interface CardProps {
    children: React.ReactNode;
}

function Card({ children }: CardProps) {
    return (
        <div className="card">
            {children}
        </div>
    );
}

Usage:

<Card>
    <h2>Employee Details</h2>
    <p>Employee information goes here.</p>
</Card>

Component communication

React commonly uses props to pass information down the component tree and callback functions to allow child components to request changes in parent-managed state.

For more distant components, Context or a shared state-management solution may be appropriate.

7. Events and Conditional Rendering

Event handling

React supports event handlers such as click, change, submit, and input. Event properties use camelCase naming.

function EmployeeButton() {
    function handleClick() {
        alert('Employee selected');
    }

    return (
        <button onClick={handleClick}>
            Select Employee
        </button>
    );
}

Passing arguments to event handlers

function EmployeeButton() {
    function handleSelect(employeeId: number) {
        console.log('Selected:', employeeId);
    }

    return (
        <button onClick={() => handleSelect(101)}>
            Select Employee
        </button>
    );
}

Handling input events

function EmployeeSearch() {
    function handleChange(
        event: React.ChangeEvent<HTMLInputElement>
    ) {
        console.log(event.target.value);
    }

    return (
        <input
            type="text"
            onChange={handleChange}
            placeholder="Search employee"
        />
    );
}

Form submit event

function EmployeeForm() {
    function handleSubmit(
        event: React.FormEvent<HTMLFormElement>
    ) {
        event.preventDefault();
        console.log('Form submitted');
    }

    return (
        <form onSubmit={handleSubmit}>
            <input placeholder="Employee name" />
            <button type="submit">Save</button>
        </form>
    );
}

Conditional rendering with state

import { useState } from 'react';

function EmployeeStatus() {
    const [isActive, setIsActive] = useState(true);

    return (
        <div>
            <p>
                {isActive ? 'Active' : 'Inactive'}
            </p>

            <button
                onClick={() => setIsActive(!isActive)}>
                Change Status
            </button>
        </div>
    );
}

8. State and React Hooks

What is state?

State is data that a component needs to remember between renders. When state is updated using the appropriate React mechanism, React renders the component again so the UI can reflect the new value.

Examples include search text, selected records, form values, modal visibility, and loading status.

What are Hooks?

Hooks are functions provided by React that allow function components to use React features such as state, context, refs, and effects.

useState

useState adds a state variable to a function component.

import { useState } from 'react';

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

    return (
        <div>
            <h2>Count: {count}</h2>

            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

Updating object state

When updating objects in state, create a new object rather than directly mutating the existing object.

const [employee, setEmployee] = useState({
    employeeName: 'Rahul',
    salary: 50000
});

function updateSalary() {
    setEmployee(previous => ({
        ...previous,
        salary: 60000
    }));
}

Updating array state

const [employees, setEmployees] = useState([
    'Rahul',
    'Amit'
]);

function addEmployee() {
    setEmployees(previous => [
        ...previous,
        'Neha'
    ]);
}

useEffect

useEffect is used to synchronize a component with an external system, such as a network connection, browser API, or third-party widget. It can also be used for data fetching when an application is designed around that approach.

import { useEffect, useState } from 'react';

function EmployeeComponent() {
    const [message, setMessage] = useState('Loading');

    useEffect(() => {
        document.title = 'Employee Management';

        setMessage('Application ready');
    }, []);

    return <p>{message}</p>;
}

The dependency array controls when the effect is re-synchronized:

Dependency configurationBehavior
No dependency arrayRuns after every committed render.
Empty array []Runs when the component mounts, with development Strict Mode caveats.
[value]Runs when the component mounts and when the dependency changes.

Effects can return cleanup functions for subscriptions, timers, and other resources.

useEffect(() => {
    const timer = setInterval(() => {
        console.log('Timer running');
    }, 1000);

    return () => {
        clearInterval(timer);
    };
}, []);
Do not use useEffect for every calculation or state update. Derived values can usually be calculated during rendering. Effects are intended for synchronizing with external systems, not for replacing normal data flow.

useRef

useRef stores a mutable value that persists between renders without triggering a render when it changes. It is also commonly used to reference DOM elements.

import { useRef } from 'react';

function EmployeeSearch() {
    const inputRef = useRef<HTMLInputElement>(null);

    function focusInput() {
        inputRef.current?.focus();
    }

    return (
        <div>
            <input ref={inputRef} />

            <button onClick={focusInput}>
                Focus Input
            </button>
        </div>
    );
}

useMemo

useMemo caches the result of a calculation between renders when its dependencies have not changed. It is useful for expensive calculations when measurement shows a benefit.

import { useMemo } from 'react';

function EmployeeSummary({ employees }) {
    const activeEmployees = useMemo(
        () => employees.filter(e => e.isActive),
        [employees]
    );

    return (
        <p>
            Active employees: {activeEmployees.length}
        </p>
    );
}

useCallback

useCallback caches a function definition between renders while its dependencies remain unchanged. It can be useful when passing callbacks to memoized child components or when a stable function reference is required.

import { useCallback, useState } from 'react';

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

    const increment = useCallback(() => {
        setCount(value => value + 1);
    }, []);

    return (
        <button onClick={increment}>
            Count: {count}
        </button>
    );
}

useReducer

useReducer manages state through a reducer function and dispatched actions. It can be useful when state transitions are more complex than a few independent state variables.

import { useReducer } from 'react';

interface State {
    count: number;
}

type Action =
    | { type: 'increment' }
    | { type: 'decrement' }
    | { type: 'reset' };

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };

        case 'decrement':
            return { count: state.count - 1 };

        case 'reset':
            return { count: 0 };

        default:
            return state;
    }
}

function Counter() {
    const [state, dispatch] = useReducer(
        reducer,
        { count: 0 }
    );

    return (
        <div>
            <p>Count: {state.count}</p>

            <button onClick={() => dispatch({ type: 'increment' })}>
                Increment
            </button>

            <button onClick={() => dispatch({ type: 'decrement' })}>
                Decrement
            </button>

            <button onClick={() => dispatch({ type: 'reset' })}>
                Reset
            </button>
        </div>
    );
}

Common React Hooks

HookPurpose
useStateManages component state.
useEffectSynchronizes with external systems.
useContextReads context values.
useRefStores mutable values and DOM references.
useMemoCaches calculation results.
useCallbackCaches function definitions.
useReducerManages state through reducer logic.
useIdGenerates IDs suitable for accessibility relationships.
Hooks must be called at the top level of React function components or custom Hooks. Do not call Hooks inside loops, conditions, or ordinary nested functions.

9. Forms and Validation

What is a controlled component?

A controlled form input is an input whose value is managed by React state. The input value is updated through an event handler.

import { useState } from 'react';

function EmployeeForm() {
    const [employeeName, setEmployeeName] = useState('');

    return (
        <div>
            <input
                value={employeeName}
                onChange={event =>
                    setEmployeeName(event.target.value)
                }
                placeholder="Employee Name"
            />

            <p>Name: {employeeName}</p>
        </div>
    );
}

Uncontrolled components

An uncontrolled input keeps its current value in the DOM rather than React state. A ref can be used to read its value when required.

import { useRef } from 'react';

function EmployeeForm() {
    const nameRef = useRef<HTMLInputElement>(null);

    function handleSubmit() {
        console.log(nameRef.current?.value);
    }

    return (
        <div>
            <input ref={nameRef} />

            <button onClick={handleSubmit}>
                Submit
            </button>
        </div>
    );
}

Form validation

React does not provide a complete built-in form validation framework. Developers can implement validation directly or use libraries such as React Hook Form and Zod.

import { useState } from 'react';

function EmployeeForm() {
    const [name, setName] = useState('');
    const [error, setError] = useState('');

    function handleSubmit(
        event: React.FormEvent<HTMLFormElement>
    ) {
        event.preventDefault();

        if (!name.trim()) {
            setError('Employee name is required');
            return;
        }

        setError('');
        console.log('Saved:', name);
    }

    return (
        <form onSubmit={handleSubmit}>
            <input
                value={name}
                onChange={event => setName(event.target.value)}
                placeholder="Employee name"
            />

            {error && <p>{error}</p>}

            <button type="submit">Save</button>
        </form>
    );
}

React Hook Form

React Hook Form is a popular library for managing forms with a focus on performance and minimal unnecessary re-renders.

Install:

npm install react-hook-form

Example:

import { useForm } from 'react-hook-form';

interface EmployeeFormData {
    employeeName: string;
    email: string;
}

function EmployeeForm() {
    const {
        register,
        handleSubmit,
        formState: { errors }
    } = useForm<EmployeeFormData>();

    function saveEmployee(data: EmployeeFormData) {
        console.log(data);
    }

    return (
        <form onSubmit={handleSubmit(saveEmployee)}>

            <input
                {...register('employeeName', {
                    required: 'Name is required'
                })}
                placeholder="Employee Name"
            />

            {errors.employeeName && (
                <p>{errors.employeeName.message}</p>
            )}

            <input
                {...register('email', {
                    required: 'Email is required'
                })}
                placeholder="Email"
            />

            {errors.email && (
                <p>{errors.email.message}</p>
            )}

            <button type="submit">Save</button>
        </form>
    );
}

Common form approaches

ApproachPurpose
Controlled inputsManage input values through React state.
Uncontrolled inputsRead values from DOM elements using refs.
React Hook FormManage forms and validation with a dedicated library.
ZodDefine and validate data schemas.
Frontend validation improves user experience, but it is not a security boundary. ASP.NET Core must independently validate incoming request data.

10. Effects and Lifecycle Concepts

React function component lifecycle

Function components are rendered by React when their inputs or relevant state change. Effects can synchronize components with external systems and return cleanup logic.

Important concepts

  • Render: React calls the component to calculate its UI.
  • Commit: React applies necessary changes to the rendered interface.
  • Effect setup: An effect runs after the relevant commit.
  • Cleanup: A returned cleanup function runs before the effect is re-synchronized and when the component is removed.

Effect with dependencies

import { useEffect, useState } from 'react';

function EmployeeDetails({ employeeId }) {
    const [employee, setEmployee] = useState(null);

    useEffect(() => {
        let ignore = false;

        async function loadEmployee() {
            const response = await fetch(
                `/api/employees/${employeeId}`
            );

            const data = await response.json();

            if (!ignore) {
                setEmployee(data);
            }
        }

        loadEmployee();

        return () => {
            ignore = true;
        };
    }, [employeeId]);

    return (
        <div>
            {employee
                ? employee.employeeName
                : 'Loading...'
            }
        </div>
    );
}

This example prevents an older request from updating state after a newer employee ID has been selected. For production data fetching, consider a dedicated data-fetching library or a framework's data-loading features.

Class components and lifecycle methods

Older React applications may use class components. Class components have lifecycle methods such as:

Class lifecycle methodPurpose
componentDidMountRuns after the component mounts.
componentDidUpdateRuns after updates.
componentWillUnmountRuns before removal.
shouldComponentUpdateCan control whether a component should update.

Modern React development generally recommends function components and Hooks for new code. Class components remain supported and are important when maintaining older projects.

11. Routing and Navigation

What is routing?

Routing maps application URLs to the components or views displayed to the user. React itself does not provide a complete built-in browser routing solution, so applications commonly use React Router or another routing library.

Install React Router

npm install react-router

Configure routes

import {
    BrowserRouter,
    Routes,
    Route,
    Navigate
} from 'react-router';

import Home from './pages/Home';
import EmployeeList from './pages/EmployeeList';
import EmployeeDetails from './pages/EmployeeDetails';

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route
                    path="/"
                    element={<Home />}
                />

                <Route
                    path="/employees"
                    element={<EmployeeList />}
                />

                <Route
                    path="/employees/:id"
                    element={<EmployeeDetails />}
                />

                <Route
                    path="*"
                    element={<Navigate to="/" replace />}
                />
            </Routes>
        </BrowserRouter>
    );
}

export default App;

Navigation links

import { Link } from 'react-router';

function Navigation() {
    return (
        <nav>
            <Link to="/">Home</Link>
            <Link to="/employees">Employees</Link>
        </nav>
    );
}

Route parameters

Route parameters identify a specific resource.

import { useParams } from 'react-router';

function EmployeeDetails() {
    const { id } = useParams();

    return (
        <p>Employee ID: {id}</p>
    );
}

Programmatic navigation

import { useNavigate } from 'react-router';

function EmployeeList() {
    const navigate = useNavigate();

    function openEmployee(employeeId: number) {
        navigate(`/employees/${employeeId}`);
    }

    return (
        <button onClick={() => openEmployee(101)}>
            View Employee
        </button>
    );
}

Nested routes and layouts

Nested routes allow child pages to render inside a shared parent layout. React Router provides an outlet component for this purpose.

import {
    Outlet,
    Link
} from 'react-router';

function EmployeeLayout() {
    return (
        <div>
            <h2>Employee Management</h2>

            <nav>
                <Link to="list">Employee List</Link>
                <Link to="reports">Reports</Link>
            </nav>

            <Outlet />
        </div>
    );
}

Protected routes

Applications can implement route-level checks to control navigation based on frontend authentication state. The exact approach depends on the routing library and application architecture.

Client-side route protection is not backend security. Every protected ASP.NET Core endpoint must independently enforce authentication and authorization.

12. API Communication

How does React communicate with a Web API?

React applications commonly use the browser's Fetch API or libraries such as Axios to communicate with backend services.

For a .NET developer, the concept is similar to a frontend client sending HTTP requests to ASP.NET Core Web API endpoints.

Fetch API

The browser provides Fetch API for making HTTP requests.

async function getEmployees() {
    const response = await fetch(
        'https://localhost:7001/api/employees'
    );

    if (!response.ok) {
        throw new Error('Unable to load employees');
    }

    return await response.json();
}

Create a TypeScript model

export interface Employee {
    employeeId: number;
    employeeName: string;
    email: string;
    salary: number;
    departmentId: number;
}

Create an API service

React does not prescribe a built-in service class pattern. A common approach is to create reusable functions in separate service modules.

import type { Employee } from '../types/employee';

const API_URL = 'https://localhost:7001/api/employees';

export async function getEmployees(): Promise<Employee[]> {
    const response = await fetch(API_URL);

    if (!response.ok) {
        throw new Error('Failed to fetch employees');
    }

    return response.json();
}

export async function getEmployeeById(
    id: number
): Promise<Employee> {
    const response = await fetch(`${API_URL}/${id}`);

    if (!response.ok) {
        throw new Error('Employee not found');
    }

    return response.json();
}

export async function createEmployee(
    employee: Employee
): Promise<Employee> {
    const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(employee)
    });

    if (!response.ok) {
        throw new Error('Failed to create employee');
    }

    return response.json();
}

export async function updateEmployee(
    id: number,
    employee: Employee
): Promise<Employee> {
    const response = await fetch(`${API_URL}/${id}`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(employee)
    });

    if (!response.ok) {
        throw new Error('Failed to update employee');
    }

    return response.json();
}

export async function deleteEmployee(
    id: number
): Promise<void> {
    const response = await fetch(`${API_URL}/${id}`, {
        method: 'DELETE'
    });

    if (!response.ok) {
        throw new Error('Failed to delete employee');
    }
}

Consume API data in a component

import { useEffect, useState } from 'react';

import { getEmployees } from '../services/employeeService';
import type { Employee } from '../types/employee';

function EmployeeList() {
    const [employees, setEmployees] =
        useState<Employee[]>([]);

    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');

    useEffect(() => {
        let ignore = false;

        async function loadEmployees() {
            try {
                const data = await getEmployees();

                if (!ignore) {
                    setEmployees(data);
                }
            } catch {
                if (!ignore) {
                    setError('Unable to load employees');
                }
            } finally {
                if (!ignore) {
                    setLoading(false);
                }
            }
        }

        loadEmployees();

        return () => {
            ignore = true;
        };
    }, []);

    if (loading) {
        return <p>Loading employees...</p>;
    }

    if (error) {
        return <p>{error}</p>;
    }

    return (
        <div>
            <h2>Employees</h2>

            {employees.map(employee => (
                <div key={employee.employeeId}>
                    {employee.employeeName}
                    - {employee.email}
                </div>
            ))}
        </div>
    );
}

export default EmployeeList;

HTTP methods

MethodPurpose
GETRetrieve resources.
POSTCreate a resource or submit an operation.
PUTReplace or update a resource representation.
PATCHPartially update a resource.
DELETEDelete a resource.

Axios

Axios is a popular HTTP client library that provides request configuration, response handling, interceptors, and other utilities.

Install Axios:

npm install axios

Example:

import axios from 'axios';

const apiClient = axios.create({
    baseURL: 'https://localhost:7001/api'
});

export async function getEmployees() {
    const response =
        await apiClient.get('/employees');

    return response.data;
}

Fetch vs Axios

FetchAxios
Built into modern browsers.Requires an additional dependency.
Does not reject automatically for HTTP error status codes.Rejects requests for non-success status codes by default.
Uses Response objects and methods such as json().Provides response data through response.data.
Supports AbortController for cancellation.Supports request cancellation through AbortController signals.
No built-in interceptor system.Provides request and response interceptors.
TypeScript generics and interfaces describe expected API data, but they do not verify that the server actually returned that structure. Validate external data when runtime guarantees are required.

13. Asynchronous Programming and Data Fetching

What is asynchronous programming?

Asynchronous programming allows an application to perform operations such as API requests without blocking the main JavaScript execution thread while waiting for a response.

React applications commonly use Promises, async/await, and data-fetching libraries to manage asynchronous work.

Loading, success, and error states

A data-driven component commonly manages three basic states:

  • Loading: The request is in progress.
  • Success: Data was received successfully.
  • Error: The request failed.

AbortController

AbortController can cancel a Fetch request. It is useful when a component no longer needs the result or when a new request replaces an older one.

useEffect(() => {
    const controller = new AbortController();

    async function loadEmployees() {
        try {
            const response = await fetch(
                '/api/employees',
                { signal: controller.signal }
            );

            if (!response.ok) {
                throw new Error('Request failed');
            }

            const data = await response.json();

            setEmployees(data);
        } catch (error) {
            if (
                error instanceof DOMException &&
                error.name === 'AbortError'
            ) {
                return;
            }

            setError('Unable to load employees');
        }
    }

    loadEmployees();

    return () => {
        controller.abort();
    };
}, []);

Data-fetching libraries

For larger applications, dedicated data-fetching libraries can reduce repetitive request and caching logic.

  • TanStack Query: Manages server-state fetching, caching, synchronization, and mutations.
  • SWR: Provides data fetching and caching using a stale-while-revalidate approach.

These libraries are particularly useful when multiple screens share server data or require background refresh and cache invalidation.

14. Context and State Management

What is Context API?

React Context allows data to be shared across a component tree without manually passing props through every intermediate component.

It is commonly used for application-wide information such as theme, current user, or locale.

Create a context

import {
    createContext,
    useContext
} from 'react';

interface UserContextType {
    userName: string;
    role: string;
}

const UserContext =
    createContext<UserContextType | null>(null);

export function useUser() {
    const context = useContext(UserContext);

    if (!context) {
        throw new Error(
            'useUser must be used within UserProvider'
        );
    }

    return context;
}

export function UserProvider({
    children
}: {
    children: React.ReactNode;
}) {
    const user = {
        userName: 'Rahul',
        role: 'Admin'
    };

    return (
        <UserContext value={user}>
            {children}
        </UserContext>
    );
}

Consume context

import { useUser } from './UserContext';

function Profile() {
    const user = useUser();

    return (
        <div>
            <p>Name: {user.userName}</p>
            <p>Role: {user.role}</p>
        </div>
    );
}

Context with useReducer

Context can be combined with useReducer to share more complex state and update logic across multiple components.

The reducer defines how actions change state, while Context makes the state and dispatch function available to descendant components.

Common state-management options

ApproachPurpose
useStateLocal component state.
useReducerState with structured update logic.
Context APIShare values across a component tree.
Redux ToolkitCentralized application state management.
ZustandLightweight shared state management.
TanStack QueryServer-state fetching and caching.

Client state vs server state

Client stateServer state
Managed by the frontend application.Owned by a backend service or database.
Examples: selected tab, modal visibility, local filters.Examples: employee records, departments, reports.
Often managed with React state or a state library.Often managed with a data-fetching and caching library.
Avoid introducing a large state-management library for every application. Start with local state and Context where appropriate, then choose a dedicated solution when application complexity requires it.

15. Custom Hooks

What is a custom Hook?

A custom Hook is a reusable JavaScript or TypeScript function that uses React Hooks to share stateful behavior between components.

Custom Hooks help avoid duplicating logic across multiple components.

Create a custom Hook

import { useState } from 'react';

export function useCounter(initialValue = 0) {
    const [count, setCount] = useState(initialValue);

    function increment() {
        setCount(value => value + 1);
    }

    function decrement() {
        setCount(value => value - 1);
    }

    function reset() {
        setCount(initialValue);
    }

    return {
        count,
        increment,
        decrement,
        reset
    };
}

Use the custom Hook

import { useCounter } from './useCounter';

function Counter() {
    const {
        count,
        increment,
        decrement,
        reset
    } = useCounter(0);

    return (
        <div>
            <p>Count: {count}</p>

            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

Common custom Hook use cases

  • Reusable API request behavior.
  • Form input handling.
  • Window size and browser event handling.
  • Authentication-related UI state.
  • Pagination and search logic.
  • Reusable component behavior.
Custom Hooks share logic, not automatically shared state. Each call to a custom Hook generally has its own Hook state unless it uses a shared store or another shared mechanism.

16. React Application Architecture

Feature-based architecture

Feature-based architecture organizes an application around business functionality. It helps keep related components, services, types, and hooks together.

Example project structure

src/
│
├── app/
│   ├── App.tsx
│   └── router.tsx
│
├── components/
│   ├── common/
│   └── layout/
│
├── features/
│   ├── employees/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   └── types/
│   │
│   └── departments/
│       ├── components/
│       ├── services/
│       └── types/
│
├── context/
├── hooks/
├── services/
├── types/
├── utils/
└── main.tsx

Important application areas

FolderResponsibility
componentsReusable UI components.
featuresBusiness-specific functionality.
servicesAPI and reusable application operations.
hooksReusable stateful React logic.
contextShared context providers.
typesTypeScript interfaces and types.
utilsReusable utility functions.

Separation of concerns

  • Components: Render UI and handle user interactions.
  • Hooks: Encapsulate reusable React behavior.
  • Services: Handle API communication and reusable operations.
  • Types: Define application data contracts.
  • Context or stores: Share state across components.
  • Routes: Define application navigation.

Environment configuration

Vite supports environment variables through files such as .env and .env.production.

Example:

VITE_API_URL=https://localhost:7001/api

Read the configuration in TypeScript:

const API_URL = import.meta.env.VITE_API_URL;
Frontend environment variables are included in the client-side application build. Never store database passwords, private API keys, signing secrets, or other confidential server credentials in them.

17. Authentication and Authorization

Authentication

Authentication verifies the identity of a user. A typical application may use username and password authentication or an external identity provider.

Authorization

Authorization determines what an authenticated user is allowed to access or perform.

Authentication vs authorization

AuthenticationAuthorization
Verifies the user's identity.Determines the user's permissions.
Usually occurs during login or token validation.Applied when protected functionality is accessed.
Example: validating user credentials.Example: allowing an administrator to delete employees.

JWT authentication flow

  1. User submits login credentials through the React application.
  2. React sends the credentials to an ASP.NET Core authentication endpoint.
  3. The backend validates the credentials.
  4. The backend issues an access token when authentication succeeds.
  5. The frontend manages the authentication session according to the chosen security design.
  6. Protected API requests include the appropriate authentication credentials.
  7. ASP.NET Core validates the credentials and applies authorization policies.

Axios request interceptor example

Axios supports interceptors that can attach authentication headers to outgoing requests.

import axios from 'axios';

const apiClient = axios.create({
    baseURL: 'https://localhost:7001/api'
});

apiClient.interceptors.request.use(config => {
    const token = sessionStorage.getItem('access_token');

    if (token) {
        config.headers.Authorization =
            `Bearer ${token}`;
    }

    return config;
});

export default apiClient;

This example demonstrates the interceptor mechanism. It does not prescribe sessionStorage as a secure production token-storage strategy.

Role-based UI

function AdminPanel({ role }: { role: string }) {
    return (
        <div>
            {role === 'Admin' && (
                <button>Delete Employee</button>
            )}
        </div>
    );
}

Security considerations

  • Use HTTPS for all application and API communication.
  • Do not expose confidential credentials in frontend code.
  • Consider the risks of browser-accessible token storage.
  • Use secure cookie settings and CSRF protections when applicable.
  • Apply server-side authentication and authorization.
  • Validate every protected API operation on the backend.
  • Do not rely on hidden buttons or frontend route restrictions for security.
React route protection and conditional UI rendering improve user experience. They do not prevent a user from directly calling a backend endpoint. ASP.NET Core must enforce the actual security rules.

18. Performance Optimization

Why optimize React applications?

Performance optimization improves rendering efficiency, application loading, responsiveness, and the overall user experience.

React.memo

React.memo can skip re-rendering a component when its props have not changed according to the default comparison.

import React from 'react';

interface EmployeeProps {
    name: string;
}

const EmployeeCard = React.memo(
    function EmployeeCard({ name }: EmployeeProps) {
        return <h3>{name}</h3>;
    }
);

useMemo and useCallback

Use these Hooks when caching a calculation or function reference provides a measurable benefit. They should not be added automatically to every component.

Code splitting

Code splitting allows application code to be loaded in smaller parts instead of loading every feature at the beginning.

React provides lazy and Suspense for component-level lazy loading.

import {
    lazy,
    Suspense
} from 'react';

const EmployeePage = lazy(
    () => import('./EmployeePage')
);

function App() {
    return (
        <Suspense fallback={<p>Loading...</p>}>
            <EmployeePage />
        </Suspense>
    );
}

Other performance practices

  • Use stable keys when rendering lists.
  • Avoid unnecessary state updates.
  • Keep component responsibilities focused.
  • Use pagination for large data sets.
  • Use virtualization for very large lists where appropriate.
  • Lazy-load larger application features.
  • Optimize images and static assets.
  • Cache server data appropriately.
  • Analyze production bundle sizes.
  • Use React Developer Tools Profiler to investigate rendering behavior.

React Compiler

React Compiler is a build-time optimization tool designed to automatically memoize components and values in supported configurations. It can reduce the need for manual memoization in applications where it is properly configured.

Measure performance before optimizing. A simpler component structure is often more valuable than unnecessary memoization.

19. Error Handling and Debugging

Handling API errors

Frontend API calls can fail because of network problems, authentication failures, validation errors, missing resources, or backend exceptions.

async function loadEmployees() {
    try {
        const response = await fetch('/api/employees');

        if (!response.ok) {
            throw new Error(
                `HTTP error: ${response.status}`
            );
        }

        const data = await response.json();

        return data;
    } catch (error) {
        console.error('API request failed:', error);
        throw error;
    }
}

Common HTTP status codes

StatusMeaning
200OK – Request succeeded.
201Created – Resource created.
204No Content – Request succeeded without a response body.
400Bad Request – Request is invalid.
401Unauthorized – Authentication is missing or invalid.
403Forbidden – User is not allowed to perform the operation.
404Not Found – Resource does not exist.
409Conflict – Request conflicts with the current resource state.
500Internal Server Error – Unexpected server-side failure.

Error boundaries

Error boundaries are React components that catch rendering errors in their descendant component tree and display a fallback UI. Traditional error boundaries are implemented using class components.

import React from 'react';

interface Props {
    children: React.ReactNode;
}

interface State {
    hasError: boolean;
}

class ErrorBoundary extends React.Component<
    Props,
    State
> {
    state: State = {
        hasError: false
    };

    static getDerivedStateFromError(): State {
        return { hasError: true };
    }

    componentDidCatch(error: Error) {
        console.error('Rendering error:', error);
    }

    render() {
        if (this.state.hasError) {
            return <h2>Something went wrong.</h2>;
        }

        return this.props.children;
    }
}

Error boundaries do not catch every kind of error. For example, errors in ordinary event handlers and asynchronous callbacks need their own error-handling approach.

Debugging tools

  • Browser Developer Tools: Inspect console messages and network requests.
  • React Developer Tools: Inspect component trees, props, state, and rendering.
  • VS Code debugger: Set breakpoints in JavaScript and TypeScript.
  • Network tab: Inspect API URLs, headers, request payloads, responses, and status codes.
  • Backend logs: Correlate frontend errors with ASP.NET Core application logs.

20. Testing React Applications

Why testing matters

Testing verifies that components and application logic work correctly. It helps detect defects and reduce regressions when code changes.

Types of testing

Testing typePurpose
Unit testingTests individual functions or components.
Integration testingTests interactions between multiple application parts.
End-to-end testingTests complete user workflows in a browser.

Common testing tools

  • Vitest: Test runner commonly used with Vite projects.
  • React Testing Library: Tests React components through user-visible behavior.
  • Jest: JavaScript testing framework used in many existing projects.
  • Playwright: Browser-based end-to-end testing.
  • Cypress: Browser-based testing and automation.

Example component test

Consider a simple counter component:

import { useState } from 'react';

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

    return (
        <div>
            <p>Count: {count}</p>

            <button
                onClick={() => setCount(value => value + 1)}>
                Increment
            </button>
        </div>
    );
}

Example test using Vitest and React Testing Library:

import {
    render,
    screen,
    fireEvent
} from '@testing-library/react';

import { describe, it, expect } from 'vitest';
import { Counter } from './Counter';

describe('Counter', () => {
    it('increments the count', () => {
        render(<Counter />);

        fireEvent.click(
            screen.getByRole('button', {
                name: 'Increment'
            })
        );

        expect(
            screen.getByText('Count: 1')
        ).toBeTruthy();
    });
});

What should be tested?

  • Component rendering.
  • User interactions and event handlers.
  • Form validation and submission.
  • API service behavior.
  • Loading and error states.
  • Navigation and protected routes.
  • Important business-related UI workflows.

21. Build and Deployment

Development vs production

DevelopmentProduction
Used during application development.Used by actual application users.
Runs through a development server.Serves optimized build output or uses a configured application framework.
Supports development diagnostics.Uses production configuration.
May include development-only tools.Uses optimized assets and production hosting settings.

Create a production build

npm run build

For a typical Vite application, the production files are generated in the dist folder.

Deployment options

  • Azure Static Web Apps: Suitable for static frontend applications and supported full-stack scenarios.
  • Azure App Service: Can host frontend files or server-side application components depending on the architecture.
  • IIS: Can host React's built static files on Windows Server.
  • Nginx: Commonly used for hosting frontend files on Linux infrastructure.
  • CDN or static hosting: Distributes frontend assets to users.

Deployment considerations

  • Configure the production API URL.
  • Configure CORS when frontend and API origins differ.
  • Configure SPA route fallback behavior where required.
  • Use HTTPS.
  • Do not include secrets in frontend environment variables.
  • Test authentication and API access after deployment.
  • Configure suitable static asset caching.
  • Monitor application errors and performance.

22. React with ASP.NET Core Web API

React and ASP.NET Core Web API can be combined to create full-stack applications. React manages the frontend interface, ASP.NET Core processes backend requests, and SQL Server stores application data.

Typical application architecture

+-----------------------------------+
|             User Browser          |
|                                   |
|          React Application        |
|                                   |
| Components | Hooks | Router       |
| State      | Forms | API Services |
+-----------------+-----------------+
                  |
                  | HTTP / HTTPS
                  v
+-----------------------------------+
|        ASP.NET Core Web API       |
|                                   |
| Controllers / Minimal APIs        |
| Authentication / Authorization   |
| Business Logic / Validation       |
+-----------------+-----------------+
                  |
                  v
+-----------------------------------+
|           Data Access Layer       |
|                                   |
|             EF Core               |
|             Dapper                |
+-----------------+-----------------+
                  |
                  v
+-----------------------------------+
|             SQL Server            |
|                                   |
|      Tables / Views / Procedures  |
+-----------------------------------+

Example ASP.NET Core endpoint

The following controller exposes an endpoint to retrieve employee information.

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    [HttpGet]
    public IActionResult GetEmployees()
    {
        var employees = new[]
        {
            new
            {
                EmployeeId = 1,
                EmployeeName = "Rahul",
                Email = "rahul@example.com"
            },
            new
            {
                EmployeeId = 2,
                EmployeeName = "Amit",
                Email = "amit@example.com"
            }
        };

        return Ok(employees);
    }
}

This example uses sample data to demonstrate the API. In a production application, the controller would normally call an application service or data-access layer.

Corresponding React model

export interface Employee {
    employeeId: number;
    employeeName: string;
    email: string;
}

Corresponding API service

import type { Employee } from '../types/employee';

const API_URL =
    'https://localhost:7001/api/employees';

export async function getEmployees(): Promise<Employee[]> {
    const response = await fetch(API_URL);

    if (!response.ok) {
        throw new Error('Unable to load employees');
    }

    return response.json();
}

Corresponding React component

import {
    useEffect,
    useState
} from 'react';

import type { Employee } from '../types/employee';
import { getEmployees } from '../services/employeeService';

function EmployeeList() {
    const [employees, setEmployees] =
        useState<Employee[]>([]);

    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');

    useEffect(() => {
        let ignore = false;

        async function loadData() {
            try {
                const data = await getEmployees();

                if (!ignore) {
                    setEmployees(data);
                }
            } catch {
                if (!ignore) {
                    setError('Unable to load employees');
                }
            } finally {
                if (!ignore) {
                    setLoading(false);
                }
            }
        }

        loadData();

        return () => {
            ignore = true;
        };
    }, []);

    if (loading) {
        return <p>Loading employees...</p>;
    }

    if (error) {
        return <p>{error}</p>;
    }

    return (
        <div>
            <h2>Employee Management</h2>

            {employees.map(employee => (
                <div key={employee.employeeId}>
                    <strong>{employee.employeeName}</strong>
                    <p>{employee.email}</p>
                </div>
            ))}
        </div>
    );
}

export default EmployeeList;

React concepts compared with .NET concepts

React conceptRelated .NET conceptExplanation
TypeScript interfaceC# DTO or interfaceDescribes the expected data structure.
ComponentUI component / presentation layerRepresents a reusable part of the interface.
Custom HookReusable service logicEncapsulates reusable frontend behavior, although its lifecycle and state model differ.
API service moduleApplication service / API clientEncapsulates communication with backend endpoints.
Context APIShared application state / dependency accessProvides shared values through the component tree.
Axios interceptorHTTP middleware conceptsProcesses frontend HTTP requests and responses.
React RouterEndpoint and navigation routing conceptsMaps browser URLs to frontend views.
Form validationASP.NET Core model validationValidates input on the client; backend validation remains essential.

Important integration considerations

  • CORS: Allow the correct frontend origin in the backend when required.
  • API contracts: Keep TypeScript types aligned with backend DTOs.
  • Validation: Validate input on both frontend and backend.
  • Authentication: Secure API endpoints independently of frontend state.
  • Error handling: Return consistent API error responses and display appropriate UI messages.
  • Configuration: Use the correct API URL for each deployment environment.
  • Versioning: Manage API changes so frontend and backend releases remain compatible.

23. React Developer Best Practices

Component design

  • Build small, reusable components.
  • Keep components focused on their responsibilities.
  • Use props for parent-to-child data flow.
  • Use callbacks for child-to-parent interaction.
  • Avoid unnecessary component complexity.

State management

  • Keep state as local as practical.
  • Use immutable state updates.
  • Use Context when values need to be shared across a component tree.
  • Choose a dedicated state-management library only when needed.
  • Separate server-state concerns from local UI state.

API and data handling

  • Keep API communication in reusable service modules.
  • Use TypeScript models for API contracts.
  • Handle loading, success, empty, and error states.
  • Use cancellation where appropriate.
  • Avoid unnecessary requests and duplicated data fetching.
  • Validate API data at runtime when required.

Security

  • Use HTTPS.
  • Do not expose secrets in frontend code.
  • Apply backend authentication and authorization.
  • Be careful when rendering untrusted HTML.
  • Follow secure session and token-management practices.
  • Keep project dependencies updated.

Maintainability

  • Use feature-based project organization where appropriate.
  • Use meaningful names for components, functions, and variables.
  • Use TypeScript for larger applications.
  • Write tests for important functionality.
  • Keep business logic out of presentation-heavy components when practical.
  • Document important API contracts and business rules.

24. Suggested Learning and Practice Path

Follow this progression to move from basic React development to building complete full-stack applications.

StageFocus areasPractice outcome
FoundationJavaScript, TypeScript, React fundamentals, JSX.Create a basic React application.
UI DevelopmentComponents, props, events, lists, conditional rendering.Build reusable user interfaces.
State ManagementuseState, useEffect, useReducer, Context, custom Hooks.Manage interactive application state.
Data HandlingForms, validation, Fetch, Axios, asynchronous programming.Build API-driven workflows.
Application StructureReact Router, feature architecture, reusable services.Organize a multi-screen application.
Security and QualityAuthentication, authorization, error handling, testing.Build a secure and testable frontend.
Full Stack IntegrationASP.NET Core Web API, DTOs, SQL Server, deployment.Build and deploy a full-stack application.

Recommended practical project

Build an Employee Management System using React, ASP.NET Core Web API, and SQL Server.

Frontend features:

  • Employee listing and details.
  • Create and edit employee forms.
  • Delete employee functionality.
  • Search, filtering, and pagination.
  • Department management.
  • Login and role-aware navigation.
  • Loading indicators and error messages.

Backend features:

  • ASP.NET Core Web API endpoints.
  • DTOs and request validation.
  • Business logic and service layer.
  • Entity Framework Core or Dapper.
  • SQL Server database.
  • Authentication and authorization.
  • Consistent API responses and error handling.

Final Thoughts

Learning React involves more than writing JSX and displaying data. It requires understanding components, props, state, Hooks, routing, forms, asynchronous operations, API communication, and application security.

For a .NET developer, React provides another powerful option for building frontend applications that communicate with ASP.NET Core Web API. Understanding how React components manage state, how TypeScript models align with backend DTOs, and how frontend requests interact with server-side authorization will help you build maintainable full-stack applications.

Start with JavaScript and TypeScript fundamentals, practise React components and Hooks, and gradually build a complete application connected to ASP.NET Core Web API and SQL Server.

What’s Next?

Use this guide as a reference while building real React applications. Practise the concepts with small components first, then combine them into a complete frontend application with API integration, authentication, validation, and deployment.

Skills Tutorials · Learn • Build • Grow Together