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.
Learning roadmap
- React fundamentals
- JavaScript fundamentals
- TypeScript with React
- Development environment and setup
- JSX and rendering
- Components and props
- Events and conditional rendering
- State and Hooks
- Forms and validation
- Effects and lifecycle concepts
- Routing and navigation
- API communication
- Asynchronous programming
- Context and state management
- Custom Hooks
- Application architecture
- Authentication and authorization
- Performance optimization
- Error handling and debugging
- Testing React applications
- Build and deployment
- React with ASP.NET Core Web API
- Developer best practices
- Learning and practice path
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
| React | Angular |
|---|---|
| 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.
| React | React 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
| Type | Interface |
|---|---|
| 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']);
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
| Command | Purpose |
|---|---|
npm create vite@latest | Creates a Vite project. |
npm install | Installs project dependencies. |
npm run dev | Starts the development server. |
npm run build | Creates a production build. |
npm run preview | Previews the production build locally. |
npm install package-name | Installs a dependency. |
npm uninstall package-name | Removes 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
classNameinstead of HTML'sclassattribute. - 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
| Props | State |
|---|---|
| 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 configuration | Behavior |
|---|---|
| No dependency array | Runs 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);
};
}, []);
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
| Hook | Purpose |
|---|---|
useState | Manages component state. |
useEffect | Synchronizes with external systems. |
useContext | Reads context values. |
useRef | Stores mutable values and DOM references. |
useMemo | Caches calculation results. |
useCallback | Caches function definitions. |
useReducer | Manages state through reducer logic. |
useId | Generates IDs suitable for accessibility relationships. |
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
| Approach | Purpose |
|---|---|
| Controlled inputs | Manage input values through React state. |
| Uncontrolled inputs | Read values from DOM elements using refs. |
| React Hook Form | Manage forms and validation with a dedicated library. |
| Zod | Define and validate data schemas. |
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 method | Purpose |
|---|---|
componentDidMount | Runs after the component mounts. |
componentDidUpdate | Runs after updates. |
componentWillUnmount | Runs before removal. |
shouldComponentUpdate | Can 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.
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
| Method | Purpose |
|---|---|
| GET | Retrieve resources. |
| POST | Create a resource or submit an operation. |
| PUT | Replace or update a resource representation. |
| PATCH | Partially update a resource. |
| DELETE | Delete 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
| Fetch | Axios |
|---|---|
| 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. |
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
| Approach | Purpose |
|---|---|
| useState | Local component state. |
| useReducer | State with structured update logic. |
| Context API | Share values across a component tree. |
| Redux Toolkit | Centralized application state management. |
| Zustand | Lightweight shared state management. |
| TanStack Query | Server-state fetching and caching. |
Client state vs server state
| Client state | Server 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. |
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.
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
| Folder | Responsibility |
|---|---|
| components | Reusable UI components. |
| features | Business-specific functionality. |
| services | API and reusable application operations. |
| hooks | Reusable stateful React logic. |
| context | Shared context providers. |
| types | TypeScript interfaces and types. |
| utils | Reusable 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;
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
| Authentication | Authorization |
|---|---|
| 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
- User submits login credentials through the React application.
- React sends the credentials to an ASP.NET Core authentication endpoint.
- The backend validates the credentials.
- The backend issues an access token when authentication succeeds.
- The frontend manages the authentication session according to the chosen security design.
- Protected API requests include the appropriate authentication credentials.
- 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.
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.
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
| Status | Meaning |
|---|---|
| 200 | OK – Request succeeded. |
| 201 | Created – Resource created. |
| 204 | No Content – Request succeeded without a response body. |
| 400 | Bad Request – Request is invalid. |
| 401 | Unauthorized – Authentication is missing or invalid. |
| 403 | Forbidden – User is not allowed to perform the operation. |
| 404 | Not Found – Resource does not exist. |
| 409 | Conflict – Request conflicts with the current resource state. |
| 500 | Internal 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 type | Purpose |
|---|---|
| Unit testing | Tests individual functions or components. |
| Integration testing | Tests interactions between multiple application parts. |
| End-to-end testing | Tests 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
| Development | Production |
|---|---|
| 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 concept | Related .NET concept | Explanation |
|---|---|---|
| TypeScript interface | C# DTO or interface | Describes the expected data structure. |
| Component | UI component / presentation layer | Represents a reusable part of the interface. |
| Custom Hook | Reusable service logic | Encapsulates reusable frontend behavior, although its lifecycle and state model differ. |
| API service module | Application service / API client | Encapsulates communication with backend endpoints. |
| Context API | Shared application state / dependency access | Provides shared values through the component tree. |
| Axios interceptor | HTTP middleware concepts | Processes frontend HTTP requests and responses. |
| React Router | Endpoint and navigation routing concepts | Maps browser URLs to frontend views. |
| Form validation | ASP.NET Core model validation | Validates 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.
| Stage | Focus areas | Practice outcome |
|---|---|---|
| Foundation | JavaScript, TypeScript, React fundamentals, JSX. | Create a basic React application. |
| UI Development | Components, props, events, lists, conditional rendering. | Build reusable user interfaces. |
| State Management | useState, useEffect, useReducer, Context, custom Hooks. | Manage interactive application state. |
| Data Handling | Forms, validation, Fetch, Axios, asynchronous programming. | Build API-driven workflows. |
| Application Structure | React Router, feature architecture, reusable services. | Organize a multi-screen application. |
| Security and Quality | Authentication, authorization, error handling, testing. | Build a secure and testable frontend. |
| Full Stack Integration | ASP.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.