Skip to content

Overview

React is a JavaScript library used to build interactive user interfaces by composing reusable components. Instead of manually updating the DOM after every change, you describe how the UI should look for a given state. React then handles efficient updates under the hood. This model improves maintainability in real projects because UI behavior stays predictable as applications grow.

Declarative UI

Describe the destination UI state, not every tiny DOM step.

Component Composition

Split screens into reusable, testable, independent building blocks.

Efficient Rendering

React computes minimal real DOM changes through reconciliation.

graph TD A["State or Props Change"] --> B["Render Phase Create New Virtual Tree"] B --> C["Reconciliation Compare Old vs New"] C --> D["Commit Phase Apply Real DOM Updates"] D --> E["Browser Paint"]

The browser DOM is powerful but expensive to manipulate frequently at scale. React creates an in-memory representation called the Virtual DOM. On each update, React compares the previous virtual tree to the new one and computes a minimal patch. This comparison process is called reconciliation.

React follows practical heuristics while diffing trees. If element types differ, React replaces the subtree. If types match, React updates changed attributes and keeps existing nodes when possible. For lists, stable keys let React map old and new items correctly, reducing unnecessary re-renders and preserving component state.

JSX is syntax sugar that lets you write UI structures close to HTML while still using JavaScript expressions. It compiles into function calls that create element objects. JSX improves readability and intent, especially in nested UI trees.

const name = "Sahil";
const heading = <h1 className="title">Hello {name}</h1>;
  • Return a single parent or fragment.
  • Use className instead of class.
  • Use {} for JavaScript expressions.
  • Close all tags explicitly.

Most modern projects use functional components with hooks. Class components still appear in legacy codebases and interviews, so understanding lifecycle methods remains useful.

function WelcomeCard({ name }) {
return (
<section>
<h2>Welcome, {name}</h2>
<p>React renders this based on current props.</p>
</section>
);
}

The Vite workflow is fast, minimal, and production-ready for React projects. Use the JavaScript template when you want simpler onboarding. Use the TypeScript template when you want static type safety and stronger tooling.

  1. Create project.

    Terminal window
    npm create vite@latest react-js-app -- --template react
  2. Move into the folder and install dependencies.

    Terminal window
    cd react-js-app
    npm install
  3. Start development server.

    Terminal window
    npm run dev
src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
src/App.jsx
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return (
<main>
<h1>React + Vite (JSX)</h1>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
</main>
);
}

Props are external inputs passed from parent to child components. State is local, mutable data owned by a component. In React, data usually flows one way: parent to child. This one-way flow keeps behavior easier to reason about and debug.

graph LR P["Parent State"] -->|props| C1["Child A"] P -->|props| C2["Child B"] C1 -->|event callback| P C2 -->|event callback| P

State updates are batched and may run asynchronously. Because of this, use functional updates when the next state depends on the previous value.

setCount((prev) => prev + 1);

React events are declared directly on elements using props like onClick, onChange, and onSubmit. These handlers receive synthetic events that behave consistently across browsers. Conditional rendering lets you show loading states, empty states, success content, and error UI without writing separate pages. Lists connect data to UI and require stable key values so React can track identity correctly between renders.

import { useState } from "react";
export default function SearchBox() {
const [query, setQuery] = useState("");
const [isLoading, setIsLoading] = useState(false);
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
console.log("Searching for:", query);
}, 800);
}
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search topic" />
<button type="submit" disabled={!query.trim() || isLoading}>
{isLoading ? "Searching..." : "Search"}
</button>
{!query && <p>Type something to start.</p>}
{query && !isLoading && <p>Ready to search for: {query}</p>}
</form>
);
}
graph TD A["User Action"] --> B["Event Handler"] B --> C["State Update"] C --> D["Re-render"] D --> E["Conditional UI + Updated List"]

A controlled component stores input value in React state and updates it through onChange. This approach centralizes form behavior, enabling validation, formatting, and conditional logic.

import { useState } from "react";
export default function NameForm() {
const [name, setName] = useState("");
return (
<form>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter your name" />
<p>Preview: {name}</p>
</form>
);
}

Class components expose lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. In function components, useEffect models setup and cleanup behavior.

graph TD M["Mount"] --> U["Update"] U --> U U --> X["Unmount"] M --> E1["Effect Setup"] U --> E2["Effect Re-run if deps changed"] X --> C["Cleanup"]
import { useEffect } from "react";
export default function ResizeLogger() {
useEffect(() => {
function onResize() {
console.log("Window resized");
}
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return <p>Open console and resize window.</p>;
}

Fragments group elements without creating extra DOM nodes.

function UserHeader({ name, role }) {
return (
<>
<h3>{name}</h3>
<p>{role}</p>
</>
);
}

Portals render children into a different DOM subtree, commonly used for modals and overlays.

import { createPortal } from "react-dom";
function ConfirmModal({ open, onClose, onConfirm }) {
if (!open) return null;
return createPortal(
<div className="overlay">
<div className="modal">
<h3>Delete item?</h3>
<p>This action cannot be undone.</p>
<button onClick={onClose}>Cancel</button>
<button onClick={onConfirm}>Delete</button>
</div>
</div>,
document.getElementById("modal-root"),
);
}

Refs provide controlled direct access to DOM nodes or imperative APIs.

import { useRef } from "react";
export default function FocusInput() {
const inputRef = useRef<HTMLInputElement | null>(null);
function focusField() {
inputRef.current?.focus();
}
return (
<div>
<input ref={inputRef} placeholder="Click button to focus" />
<button onClick={focusField}>Focus Input</button>
</div>
);
}

Error boundaries catch rendering errors in part of the tree and show fallback UI instead of crashing the whole app.

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error("UI error:", error, info);
}
render() {
if (this.state.hasError) {
return <p>Something went wrong. Please refresh.</p>;
}
return this.props.children;
}
}
graph LR A["Child Component Throws Error"] --> B["Error Boundary Catches"] B --> C["Fallback UI Rendered"] C --> D["Rest of App Continues"]

React performance is mostly about reducing unnecessary work, not avoiding all re-renders. Memoization tools such as React.memo, useMemo, and useCallback should be used where profiling shows measurable benefit.

React Fiber allows work to be split into units so rendering can be prioritized. High-priority interactions can be handled quickly, while non-critical updates can wait, improving perceived responsiveness.

graph TD A["Incoming Updates"] --> B["Scheduler"] B -->|High Priority| C["Urgent UI Work"] B -->|Normal Priority| D["Standard Updates"] B -->|Low Priority| E["Deferred Work"]

Strict Mode, Synthetic Events, and Code Splitting

Section titled “Strict Mode, Synthetic Events, and Code Splitting”

React.StrictMode helps detect side-effect-related issues during development and encourages safer patterns. React also normalizes browser events using synthetic events, giving a consistent event API across environments. For bundle performance, code splitting with React.lazy and Suspense loads feature code only when needed.

import { Suspense, lazy } from "react";
const ProfilePanel = lazy(() => import("./ProfilePanel"));
export default function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<ProfilePanel />
</Suspense>
);
}

Direct State Mutation

Create new objects and arrays instead of mutating existing state.

Unstable Keys

Use stable IDs for list keys so React can preserve identity correctly.

Heavy Parent Components

Split large components and memoize only where profiling justifies it.