Declarative UI
Describe the destination UI state, not every tiny DOM step.
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.
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>;const name = "Sahil";
const heading = React.createElement("h1", { className: "title" }, "Hello ", name);className instead of class.{} for JavaScript expressions.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> );}class WelcomeCard extends React.Component { render() { return ( <section> <h2>Welcome, {this.props.name}</h2> <p>Class components expose lifecycle methods.</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.
Create project.
npm create vite@latest react-js-app -- --template reactMove into the folder and install dependencies.
cd react-js-appnpm installStart development server.
npm run devimport 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>,);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> );}Create project with TypeScript template.
npm create vite@latest react-ts-app -- --template react-tsInstall dependencies.
cd react-ts-appnpm installRun locally.
npm run devimport React from "react";import ReactDOM from "react-dom/client";import App from "./App.tsx";import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <App /> </React.StrictMode>,);import { useState } from "react";
export default function App() { const [count, setCount] = useState<number>(0);
return ( <main> <h1>React + Vite (TSX)</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.
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> );}type Task = { id: string; title: string; done: boolean;};
type TaskListProps = { tasks: Task[];};
export function TaskList({ tasks }: TaskListProps) { const completed = tasks.filter((task) => task.done).length;
return ( <section> <p> Completed: {completed} / {tasks.length} </p>
{tasks.length === 0 ? ( <p>No tasks yet.</p> ) : ( <ul> {tasks.map((task) => ( <li key={task.id}> <span>{task.done ? "✅" : "⬜"}</span> {task.title} </li> ))} </ul> )} </section> );}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.
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; }}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.
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.