Declarative UI
You describe what the final UI should look like, not every small step needed to get there.
React is a JavaScript library used to build interactive user interfaces by putting together small, reusable pieces called components. Instead of manually updating the webpage (DOM) every time something changes, you just describe what the UI should look like for a certain state. React then takes care of updating things in the background. This way of working makes real projects easier to manage, because as your app grows bigger, the UI behavior stays predictable and easy to understand.
Declarative UI
You describe what the final UI should look like, not every small step needed to get there.
Component Composition
Break your screens into small, reusable, and testable building blocks.
Efficient Rendering
React figures out the smallest number of changes needed in the real DOM, using a process called reconciliation.
The browser’s DOM is powerful, but it becomes slow and costly if you keep changing it again and again at a large scale. So React creates a copy of it in memory, called the Virtual DOM. Every time something updates, React compares the old virtual tree with the new one, and figures out the smallest possible change needed. This comparing process is called reconciliation.
React follows some simple, practical rules while comparing the old and new trees. If the element type is different, React just replaces that whole part. If the type is the same, React updates only the attributes that changed, and keeps the existing parts as they are wherever it can. For lists, having stable key values helps React correctly match old items with new ones, which avoids extra re-renders and keeps each component’s state intact.
JSX is a simpler way of writing UI structures, almost like HTML, while still letting you use JavaScript inside it. Behind the scenes, it gets converted into function calls that create element objects. JSX makes your code easier to read and understand, especially when your UI has many nested parts.
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.{} whenever you want to write a JavaScript expression.Most modern projects use functional components along with hooks. Class components are still found in older codebases and are sometimes asked about in interviews, so it’s still useful to understand how lifecycle methods work.
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 setup process is fast, simple, and ready to be used for real production projects. Use the JavaScript template if you want a simpler, easier start. Use the TypeScript template if you want type safety (catching mistakes early) and better tooling support.
Create the project.
npm create vite@latest react-js-app -- --template reactGo into the folder and install the packages.
cd react-js-appnpm installStart the 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 the project using the TypeScript template.
npm create vite@latest react-ts-app -- --template react-tsInstall the packages.
cd react-ts-appnpm installRun it 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 values passed from a parent component down to a child component. State is local data that belongs to a component, and it can change over time. In React, data usually flows in just one direction: from parent to child. This one-way flow makes it much easier to understand and debug how your app behaves.
State updates are grouped together (batched) and may not happen instantly. Because of this, when your next state depends on the previous value, it’s best to use a functional update.
setCount((prev) => prev + 1);In React, you add events directly on elements using props like onClick, onChange, and onSubmit. These event handlers receive something called a synthetic event, which behaves the same way across all browsers. Conditional rendering lets you show loading states, empty states, success messages, and error messages, all without needing separate pages for each. Lists connect your data to the UI, and they need stable key values so React can correctly keep track of each item 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 is one where the input’s value is stored in React state, and it gets updated using onChange. This approach keeps all your form behavior in one place, making it easier to add 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 have lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. In function components, the useEffect hook handles both the setup and the 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 let you group elements together without adding any extra nodes to the DOM.
function UserHeader({ name, role }) { return ( <> <h3>{name}</h3> <p>{role}</p> </> );}Portals let you render children into a totally different part of the DOM. This is commonly used for things like 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 give you controlled, direct access to DOM elements or to special built-in functions (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 a part of your app and show a fallback message, instead of letting the whole app crash.
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 cutting down on unnecessary work, not trying to avoid every single re-render. Tools like React.memo, useMemo, and useCallback should only be used in places where you’ve actually measured and seen a real benefit.
React Fiber breaks rendering work into small units, so React can decide what to prioritize. Important, urgent interactions can be handled quickly, while less important updates can wait their turn. This makes the app feel more responsive to the user.
React.StrictMode helps you catch side-effect-related issues early, during development, and encourages safer coding habits. React also makes browser events consistent by using synthetic events, so the event behavior stays the same across different browsers. To make your app load faster, you can use code splitting with React.lazy and Suspense, so feature code only loads when it’s actually 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
Always create new objects and arrays instead of directly changing existing state.
Unstable Keys
Use stable IDs as list keys, so React can correctly keep track of each item.
Heavy Parent Components
Break large components into smaller ones, and use memoization only where you’ve actually measured a real benefit.