React Foundations
From first principles to live data — createElement, JSX, Babel, components, props, keys, useState, useEffect, Virtual DOM, reconciliation, CORS, shimmer UI, and controlled inputs.
A running set of session notes — what React actually is, and the tooling that turns it into a production app. Basics kept brief, gotchas surfaced, interview Q&A in every section.
React is just JavaScript — a small library, not a framework. It hands you helper functions to build UI and update the DOM efficiently. Everything below is plain JS objects and function calls.#
Two packages, one job each#
React comes as two separate packages because the core doesn't care where it draws — the same core powers the browser, mobile (React Native), and more. Each environment just swaps in its own "renderer."
The core (the brain)
Creates elements and components and works out what changed. Knows nothing about the browser or DOM.
The renderer (the hands)
Takes the core's output and puts it on the actual web page. The browser-specific half.
An element is an object, not HTML#
The most important mental model. createElement does not create an <h1> — it returns a plain JS object describing one. Log it and you'll see type and props, never markup.
// type, props (attributes), children const heading = React.createElement( "h1", { id: "title" }, // 2nd arg = props/attributes "Hello, world" // 3rd arg = children ); // => { type:"h1", props:{ id:"title", children:"Hello, world" } }
The tag
"h1", "div"… or a component.
Attributes
An object. Anything passed lands in props.
Contents
String, one element, or an array. Also lives in props.children.
The root & render#
You give React one DOM node to own. It renders inside that node and nowhere else.
const root = ReactDOM.createRoot(document.getElementById("root")); root.render(heading);
- render() replaces, it does not append. Whatever already lives inside the root is wiped on first render.
- React only controls its root subtree. Markup above or below
#rootis untouched — which is what lets React drop into one widget or card. - createRoot lives in react-dom, createElement in react. Building UI vs. committing it to the DOM are separate concerns.
Nesting, siblings & why JSX exists#
Nest by passing a child element as arg 3. Siblings = pass an array.
React.createElement("div", { id: "parent" }, [ React.createElement("h1", {}, "Heading 1"), React.createElement("h2", {}, "Heading 2"), ]);
Two levels deep and it's already unreadable. That pain is the whole reason JSX exists — sugar that compiles straight back to these createElement calls. React never requires JSX.
Why it's fast · Library vs Framework#
Changing the real page (the DOM) is the slowest thing a browser does. So React keeps a copy of the UI as plain JS objects (the Virtual DOM), works out the smallest set of changes needed, and only then touches the real page.
● Library
Bare-bones. Drop into a small portion of an existing app — even alongside jQuery. You choose the tooling.
● Framework
Opinionated and all-in. Generally expects you to build the whole app its way, batteries included.
Gotchas & nice-to-knows#
Import / script order
Dependencies load before code that uses them — react & react-dom before your app, or you hit React is not defined.
Sibling arrays need keys
Rendering a list of elements warns about a missing key. A key is a unique label that helps React tell the items apart between updates.
An element is immutable
Once created it's a frozen snapshot. To change UI you create new elements and render again — never mutate in place.
The secret internals
React exposes an object literally named __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED. Real, intentionally unusable.
Library = a power tool you pick up and use where you want; framework = a factory assembly line you step into and follow. Or: with a library you call it; with a framework it calls you (inversion of control).
Interview Q&A#
Q.Is React a library or a framework? Why does it matter?
Q.What does React.createElement return?
Q.What's the difference between react and react-dom?
react is the brain — it creates elements and figures out what changed. react-dom is the hands for the browser — it puts that onto the actual page. They're split because React also runs elsewhere (e.g. React Native uses a different "hands" package).Q.Does render() append or replace?
Q.What is the Virtual DOM and why is it fast?
One-line recall#
React alone is not what makes an app fast. A bundler (here, Parcel) does the heavy lifting — bundling, minifying, caching, tree-shaking. create-react-app just hides all of this; here we wire it by hand.#
npm — the package manager#
npm is a giant online store of reusable code packages, plus the tool that installs and version-manages them in your project. ("Package" and "dependency" mean the same thing — code your project relies on.) Fun aside: npm isn't officially short for "Node Package Manager," though that's exactly what it does.
Adopt npm
Walks you through a few prompts and generates package.json. -y skips the questions.
Add a package
npm i is shorthand for npm install. Pulls code from the registry into node_modules.
The four artifacts npm creates#
The shopping list
Your project's settings + the packages you want, with allowed version ranges. Commit it.
The receipt
The exact version of every package actually installed (including their dependencies), with a hash to verify it. Commit it.
The actual code
The real downloaded code of every package and its dependencies. Huge. Never commit — rebuild it with npm install.
What stays out
Lists things git should skip: node_modules, /dist, .parcel-cache.
- Golden rule: anything you can regenerate doesn't belong in git. Commit the recipe (package + lock), not the result (node_modules, dist, cache).
Version ranges: caret vs tilde#
The symbol before a version in package.json controls auto-upgrades on the next install. Versions are MAJOR.MINOR.PATCH.
Minor + patch
^2.8.3 → accepts 2.x.x, not 3.0.0. The safe default.
Patch only
~2.8.3 → accepts 2.8.x only. More conservative.
Exact pin
2.8.3 → never auto-upgrades. Total control.
- Why caret is the default: minor/patch upgrades are meant to be backward-compatible; major bumps can break your app. The lock file is what guarantees everyone actually installs the same resolved version regardless of the range.
dev vs normal dependencies#
● devDependencies — -D
Needed only while developing/building: bundlers, test runners. npm i -D parcel.
● dependencies
Needed at runtime / in production: React itself. npm i react react-dom (no flag).
Transitive dependencies: your dependency has its own dependencies, which have theirs — a whole tree. Every package carries its own package.json. That tree is why node_modules balloons.
React via npm, not CDN#
Swapping the CDN <script> tags for installed packages, and pulling them in with import.
import React from "react"; import ReactDOM from "react-dom/client"; // note: /client for createRoot
- Why npm beats CDN: no extra network call (it's local in
node_modules), and version management lives inpackage.jsoninstead of a hard-coded URL you'd hand-edit on every upgrade. - The import resolves to node_modules.
from "react"means "the react package in node_modules" — the same role the CDN global used to play.
npx & running Parcel#
npm installs packages; npx executes one. Parcel is a zero-config bundler.
# dev: build + serve at localhost:1234 with HMR npx parcel index.html # production: optimized build into /dist npx parcel build index.html
index.html is the source Parcel starts from.
Output written to /dist, served on a local port.
Edits hot-reload the page instantly.
What Parcel actually does#
Hot Module Replacement
Runs a local server and, when you save, updates the page instantly — it watches your files for changes (the watcher is written in C++ for speed).
.parcel-cache
Remembers previous build work, so each rebuild after the first drops to a few milliseconds.
Many files → few
Combines all your files into a handful (one HTML, one CSS, one JS) for production.
Smaller payload
Removes whitespace, shortens variable names, and compresses output so it downloads faster.
Removes unused code
If you import a library but use only part of it, the unused parts are dropped from the final bundle.
Images, splitting, HTTPS
Optimizes images, splits big bundles into smaller chunks, shows friendly error messages, can serve over HTTPS.
Browserslist & differential bundling#
In package.json you list which browsers your app must support. Parcel and Babel read this and add only the extra compatibility code those browsers need.
"browserslist": [ "last 2 chrome versions", "last 2 firefox versions" ]
- It's a promise, not a blocklist. The browsers you list are guaranteed to work; others probably still work, just not promised. Fewer targets = lighter, faster bundle.
- Differential bundling = Parcel makes a modern build for new browsers and an older-compatible build for old ones, and each browser loads the right one. A bank/government site might aim for ~99% browser coverage; an internal dev tool can settle for ~80%.
Gotchas & nice-to-knows#
"Browser scripts can't have imports"
Add type="module" to the <script> so the browser treats app.js as an ES module.
Remove "main" for parcel build
The auto-generated "main": "app.js" in package.json conflicts with Parcel's HTML entry — delete it.
react-dom/client
Import createRoot from react-dom/client, not react-dom, or you get a warning.
Local ≠ server node_modules
Server runs its own npm install from the lock file — the integrity hash guarantees identical versions ("works on my machine" fix).
Two build flavors
Dev build is fast + light on optimization; production build is slower but minified, tree-shaken, hashed.
Parcel is a manager too
It doesn't do everything itself — it orchestrates helper libs (Babel, etc.), the same way it has its own transitive deps.
package.json = the shopping list (what + acceptable versions). package-lock.json = the receipt (exact items + barcodes you actually bought). node_modules = the pantry full of the actual groceries — you never ship the pantry, you re-shop from the list with npm install.
Interview Q&A#
Q.package.json vs package-lock.json?
package.json is your shopping list — it says which packages you want and roughly which versions (e.g. ^2.8.3). package-lock.json is the receipt — it records the exact version of every package that got installed, plus a hash to verify it. The lock file makes sure your machine and the server install the same thing.Q.Should you commit node_modules? Why not?
package.json + lock file), and npm install recreates node_modules anywhere. Simple rule: if you can regenerate it, don't put it in git — same for /dist and .parcel-cache.Q.What do ^ (caret) and ~ (tilde) mean?
MAJOR.MINOR.PATCH. Caret ^2.8.3 = auto-update minor and patch (stays below 3.0.0). Tilde ~2.8.3 = auto-update patch only (stays below 2.9.0). Caret is the safe default: minor/patch are meant to be safe, while a major version can break things.Q.dependencies vs devDependencies?
npm i -D) are only needed while building (Parcel, test tools, linters) and aren't shipped to users. It's mainly about keeping "build-time" and "run-time" tools separate.Q.npm vs npx?
npm installs a package; npx runs one. So npm i -D parcel downloads Parcel, and npx parcel index.html executes it.Q."My React app is fast" — what's actually making it fast?
Q.What is a transitive dependency?
One-line recall#
JSX is not React and not HTML — it's an HTML-like syntax that Babel transpiles into React.createElement calls. A component is just a function that returns JSX.#
npm scripts#
Stop typing npx parcel by hand. Define scripts in package.json — the industry-standard way to start/build any project. Lost in a new codebase? Read its scripts to learn how to run it.
"scripts": { "start": "parcel index.html", // dev "build": "parcel build index.html" // prod }
- Run with
npm run <name>. e.g.npm run start,npm run build. startis special.npm startworks as a shortcut fornpm run start; other scripts need the explicitrun(npm buildwon't work).
JSX is not HTML (and not React)#
Creating elements with React.createElement is clumsy and unreadable when nested. JSX is the developer-friendly syntax Facebook built to fix that. Both produce the identical React element object — console-log them and they're the same.
// JSX const heading = <h1 id="title">Namaste React</h1>; // ...is exactly the same React element as: const heading = React.createElement("h1", { id: "title" }, "Namaste React");
- It only looks like HTML. The browser can't actually run JSX — it understands plain JavaScript only. A build step converts JSX first (next section).
- Write code for humans first, machines second. That's the whole point of JSX — it's far easier to read than nested
createElementcalls.
JSX differs from HTML#
not "class"
Use className instead of class for CSS classes.
tabIndex, onClick
Attributes are camelCased: tabIndex, not tabindex.
Wrap in parens
Multi-line JSX must be wrapped in ( ) so Babel knows where it starts/ends.
Babel & the transpile pipeline#
JSX works because Babel (a JS compiler/transpiler that Parcel installs and orchestrates) converts it before it ever reaches the JS engine.
JSX
→ React.createElement
→ React element (object)
→ HTML on the DOM
- Babel does more than JSX. It also rewrites modern JavaScript into older syntax so it runs in older browsers (using your Browserslist from Session 2). Try it live at
babeljs.io→ "Try it out." - How it converts: Babel reads your code piece by piece, builds a structured map of it (an AST, or abstract syntax tree), then writes out the equivalent plain JavaScript. You don't need to know the internals — just that it's a reliable translator.
Components: functional vs class#
A functional component is just a JS function that returns JSX. That's the entire definition. Everything on a page — button, header, card, list — is a component.
const Title = () => ( <h1 className="head">Namaste React</h1> );
● Functional — modern
A function returning JSX. The current standard; ~99.99% of new code. Use this.
● Class-based — legacy
Uses JS classes. Old style, still found in legacy codebases & some interviews.
Rendering & composition#
- Component names must be Capitalized. Lowercase is treated as a DOM tag — capital tells Babel/React it's a component.
- Render a component as
<Title />, notTitle. An element renders as{element}; a component renders as a tag. - Component composition = putting a component inside another (
<Title />insideHeading). That's the fancy interview term for the obvious thing. <Title />,<Title></Title>, and{Title()}all work — the last because a component is, at the end of the day, just a function you can call.
JavaScript inside JSX#
The superpower: inside JSX, { } runs any JavaScript expression — variables, math, function calls, even another React element or component.
const data = 10000; const Heading = () => ( <div> {data} // variable <h2>{100 + 200}</h2> // expression => 300 {title} // a React element {Title()} // or call the component </div> );
Gotchas & nice-to-knows#
JSX sanitizes → blocks XSS
Data inside { } is escaped/sanitized before render, so malicious API data can't run as a cross-site scripting attack. Free protection.
It's still JavaScript order
const can't be used before initialization. Composing two components into each other = infinite loop that freezes the tab.
Functions need explicit return
Arrow with ( ) implicitly returns JSX; an arrow/normal function with { } body needs an explicit return.
Normal functions work too
Arrow functions are the convention, but function Title(){ return ... } is equally valid.
Prettier + helpers
Prettier auto-formats on save; Bracket Pair Colorizer, ESLint, Better Comments ease development.
"not rendered" fallback
Some put a placeholder in the root; if React fails to render you'll see it — a quick render-failure signal.
JSX is a translator's draft. You write in a human-friendly language (JSX); Babel is the translator that converts it to the "official" language the engine accepts (React.createElement). The engine never sees your draft — only the translation. And a component is a recipe (function); rendering <Title/> is cooking the dish (calling it).
Interview Q&A#
Q.Is JSX HTML? Is it part of React?
React.createElement. JSX is simply a nicer, more readable way to write that same thing.Q.How does JSX work if browsers can't read it?
React.createElement calls — which the browser does understand. So by the time the JS engine sees it, the JSX is already gone. Parcel runs Babel for you behind the scenes.Q.What is Babel and what else does it do?
Q.React element vs component?
createElement produces). A component is a function that returns elements. Element = the thing; component = the function that makes it. You use a component as <Comp /> and an element as {element}.Q.Why must component names be capitalized?
<div>) means a normal HTML element; a Capitalized tag (<Header>) means "this is my component — call that function." Lowercase your component and React looks for an HTML tag by that name instead.Q.How does JSX protect against XSS?
{ } is treated as plain text, not code. So if bad data tries to sneak in a <script>, React shows it as harmless text instead of running it. You get this protection for free. (dangerouslySetInnerHTML turns it off — the scary name is the warning.)Q.Functional vs class components — which and why?
One-line recall#
Build UI by composing reusable components and feeding them data via props. Render lists with .map() + a stable key, and let a config (data) drive what the UI shows.#
Plan before you code#
Senior move: design the UI before writing any JSX. Sketch a wireframe/mock, then derive the component tree from it. Good planning makes the code almost write itself.
Logo + Nav items (Home, Cart…)
Search + RestaurantContainer → RestaurantCard ×N
Copyright, links, contact
- Two levels of planning: first the visual layout, then break it into a component hierarchy. Naming the components up front is half the work.
Composing components#
Each piece is a functional component; the app is just components nested inside components (composition).
const AppLayout = () => ( <div className="app"> <Header /> <Body /> <Footer /> </div> ); root.render(<AppLayout />);
- Reusability is the reason to extract a component. The moment you'd repeat markup (e.g. many restaurant cards), make it its own component — same logic as extracting a function.
Three ways to style#
className
A separate .css file + className. The default here.
style = JS object
style={{ }} — a JS object with camelCased properties. Not preferred.
Tailwind, MUI…
Utility classes or component libraries (covered later).
// inline = JS object, hence the double braces <div style={{ backgroundColor: "#f0f0f0" }}> … </div>
- Double braces explained: outer
{ }= "JS goes here", inner{ }= the object literal. Properties are camelCase (backgroundColor, notbackground-color).
Props — passing data to components#
Props (properties) are just the arguments you pass to a component function. React collects everything you pass into a single props object.
// pass props (like function arguments) <RestaurantCard resName="KFC" cuisine="Burgers" /> // receive them — two equivalent styles const RestaurantCard = (props) => ...props.resName... const RestaurantCard = ({ resName, cuisine }) => ... // destructure on the fly
- Destructuring on the fly (
{ resName }) is plain JavaScript object destructuring — not React magic. - Pass whole objects, not 10 separate props.
<Card resData={obj} />keeps the call clean; read it asprops.resData. Note the{ }— you're passing a JS object, not a string.
Config-driven UI#
Big idea: you build the components once, and the data decides what shows up. Swiggy sends different offers and cards for Delhi vs. Mumbai — same app, different data, different page. If a section has no data, it simply doesn't appear. The data that controls this is the "config."
Config-driven UI = a stage that follows a script. The stage (your components) is built once; the script (JSON config) decides which actors appear, in what order, tonight in Delhi vs. Mumbai. Change the script, not the stage.
- UI layer + data layer. A strong front-end engineer owns both — you should be able to question an API's shape, not treat data as "the backend's problem."
- Real data is messy & deeply nested (e.g.
resData.data.name). Use optional chaining (?.) to read it safely, and watch for redundant/duplicated fields in real APIs.
Rendering lists with map()#
Don't hand-write N cards. Loop the data array with .map() inside JSX and return a component per item.
<div className="res-container"> {resList.map((restaurant) => ( <RestaurantCard key={restaurant.data.id} resData={restaurant} /> ))} </div>
- Prefer map/filter/reduce over
forloops — functional style is the React norm and reads cleaner inside JSX. - Build display strings in JSX, not in the API. e.g.
{cuisines.join(", ")}or{"₹" + costForTwo/100 + " for two"}.
Keys — and why index is an anti-pattern#
Every item you render in a .map() needs a unique key. It's not decoration — the key is how React identifies each item, so when the list changes it can update just the one that changed instead of redrawing all of them.
● Unique id (best)
key={item.id}. React updates only what changed; inserting one item re-renders one card, not the whole list.
● Index (last resort)
key={index}. Works, but if the list reorders/inserts, indexes shift and React mis-associates items — bugs + wasted renders.
- Priority: unique id > index > no key. No key is unacceptable; index is a fallback only when no id exists (ask the backend for one).
Gotchas & nice-to-knows#
className, not class
Easy slip coming from HTML — React warns "Invalid DOM property `class`." Use className.
Numbers, not NaN children
Reading a wrong path returns undefined → "Received NaN for children." Verify the data path (resData.data.x).
Images live on a CDN
APIs send an image id; build the URL as {CDN_URL + imageId} (string concat inside { }).
Optional chaining
resData?.data?.name avoids crashes when an intermediate value is missing.
Whole object vs string prop
resData={obj} passes the object; resData="obj" passes the literal string "obj".
Destructure to clean up
Pull fields once at the top: const { name, cuisines } = resData.data; — beats repeating resData.data. everywhere.
Interview Q&A#
Q.What are props?
props object. They're read-only: a component uses its props but never changes them. Data flows one way, parent → child.Q.Why does React need a key when rendering lists?
Q.Why is using array index as a key an anti-pattern?
0 now points to a different item. React gets confused about which is which, leading to wrong updates. Only safe for a list that never changes order and has no id.Q.What is config-driven UI?
Q.How do you style components in React?
className, inline styles using a JS object (style={{ }}), or a library like Tailwind or styled-components. CSS files are the usual choice; inline is fine for small one-offs.Q.What is destructuring props "on the fly"?
({ resName, cuisine }) => … instead of writing props.resName every time. It's just normal JavaScript — same result, cleaner to read.Q.Can you pass an object as a single prop?
<Card resData={obj} />. It's cleaner than passing ten separate props. You then read it as props.resData. The curly braces matter: they pass the real object, whereas quotes would pass the text "obj".One-line recall#
A normal variable can't update the screen. A state variable (made with useState) can — because whenever state changes, React re-renders the component and syncs the UI to the data.#
Why React (when plain HTML/JS can do it)?#
Honest truth: anything React does, you could do with plain HTML/CSS/JS — React is JavaScript under the hood. The point of a library is developer experience: write less code, build more, and let React handle the hard part — keeping the screen in sync with your data, fast.
Project structure: one component per file#
A 2,000-line App.js is unreadable. Split each component into its own file. The common convention: a src/ folder for source code, a components/ folder inside it, and a utils/ folder for shared things.
All source code
Convention, not a rule. Keeps the project root clean.
One file per component
Header.js holds the Header component — file name matches the component, Capitalized.
Shared bits
Constants (constants.js), mock data, helpers — lowercase names, since they aren't components.
- Never hard-code data inside a component file. Move constants (CDN URL, logo) and mock data into
utils/so they're reusable and the component stays clean. - Keep files small — a rough industry habit is to break a component up once it grows past ~100–200 lines. React itself has no opinion on folder structure; pick something simple and refactor as you grow.
.jsvs.jsxdoesn't matter. Either works; just be consistent. Don't overthink it.
Import / export: default vs named#
To use a component across files, you export it from its file, then import it where you need it (give before you take). Two flavors:
// DEFAULT — one per file export default Header; import Header from "./components/Header"; // NAMED — as many as you want, names must match export const CDN_URL = "..."; import { CDN_URL } from "../utils/constants"; // note the { }
- Default = the file's main thing (usually a component); import it with any name, no braces. Named = pick specific things by exact name, wrapped in
{ }. A file can have one default but many named exports. - The file extension is optional on import.
"./components/Header"resolves to the.jsfile automatically.
Event handlers#
Make the UI interactive by passing a function to events like onClick. The function runs when the event fires.
<button onClick={() => {
console.log("clicked");
}}>Top Rated</button>- Pass the function, don't call it.
onClick={handleClick}— React calls it on click. Every DOM event has a React equivalent (onClick,onMouseOver,onChange…), all camelCased.
The core problem: a normal variable can't update the UI#
Say a button filters a list. You can write the filter in plain JS and the data changes — but the screen doesn't budge. A normal let variable has no way to tell React "I changed, redraw me." That gap is exactly what React's state solves.
A normal variable is a whiteboard in a locked room — you can change it, but no one outside sees. A state variable is a live scoreboard — change the number and the display updates automatically for everyone watching.
useState — the first hook#
A hook is just a JavaScript function React gives you with a built-in superpower. useState creates a state variable: a value React watches, plus a setter function to change it.
import { useState } from "react"; // named import // [ current value, function to update it ] const [listOfRes, setListOfRes] = useState(resList); // arg = initial value
- The two parts:
listOfResis the current value;setListOfResis the only correct way to change it. The argument touseStateis the starting value. - Never reassign state directly.
listOfRes = [...]does nothing useful — you must callsetListOfRes(newValue), which is what tells React to re-render. - State is local to its component — scoped to that function, like any local variable.
The one rule: state change → re-render#
This is the heart of React: whenever a state variable changes, React re-renders that component and updates the screen to match. Call the setter, and the UI follows automatically — you never touch the DOM yourself.
User clicks a button
You call the setter with new data
React re-runs the component
Screen matches the new state
Under the hood: Virtual DOM & reconciliation#
When people say "React is fast," they mean it's efficient at updating the DOM — the slow part of any web page. Here's how.
A JS copy of the UI
A lightweight object representing the real DOM. (A React element is exactly this kind of object.)
Old vs new copy
On a state change React builds a new Virtual DOM and compares it to the old one — like a "git diff" between two objects.
Update only the diff
React then changes only the real DOM nodes that actually differ, leaving the rest untouched.
- Why this is fast: comparing two plain JS objects is cheap; rewriting real HTML is expensive. React diffs the cheap copies and touches the real DOM as little as possible.
- React Fiber is the name of React's current reconciliation engine (rewritten in React 16). It enables splitting rendering work into chunks. Interview-ready phrasing: "React does efficient DOM manipulation via a Virtual DOM and a diffing algorithm."
Gotchas & nice-to-knows#
Import useState from react
It's a named import: import { useState } from "react" — braces required.
The setter, not =
Updating state with listOfRes = x won't re-render. Use setListOfRes(x).
The [ ] is destructuring
const [a, setA] = useState() — useState returns an array; you're just array-destructuring it. Plain JS, no magic.
Name the setter set+Name
For listOfRes, call it setListOfRes. Not enforced, but everyone follows it.
Duplicate keys when copying
Reused mock data can give two cards the same key — React warns. Give each a unique id.
Virtual DOM predates React
The idea (an object representation of the DOM) existed before; React popularized it and built its diffing algorithm on top.
Interview Q&A#
Q.What is a React hook?
useState and useEffect. They're just normal JS functions with logic written inside React.Q.What does useState do?
[value, setValue]. The argument you pass is the starting value.Q.Why can't I just update a normal variable to change the UI?
Q.What happens when state changes?
Q.What is the Virtual DOM?
Q.What is reconciliation / React Fiber?
Q."Why is React fast?" (the interview line)
One-line recall#
Render the page first, then fetch. useEffect runs your API call after render; you drop the data into state, and React re-renders with the real content.#
Monolith vs microservices#
Two ways to structure a whole application's backend — useful context for where a React app fits.
● Monolith
One big project holds everything — UI, APIs, auth, DB, notifications. Change one button and you rebuild/redeploy the whole thing.
● Microservices
Separate small services, each with one job, its own tech stack, and its own deploy cycle. They talk to each other over the network.
- Separation of concerns / single responsibility — each service does one thing. Your React app is just the UI microservice; it calls a backend service to get data.
- Services connect by URL/port — e.g. UI on
:1234, backend mapped to/api. Mix stacks freely (React UI, Java backend, etc.).
When to fetch: two approaches#
When should the API call happen relative to rendering? React favors the second option.
● Load → fetch → render
Wait for the data, then show the page. User stares at a blank/frozen screen until it arrives.
● Render → fetch → re-render (React way)
Show the page (skeleton) instantly, fetch in the background, then re-render with data. Better UX.
- Two renders is fine. React's render cycle is fast and cheap, so rendering twice for a smoother experience is a good trade.
useEffect — run code after render#
useEffect takes two arguments: a callback function and a dependency array. The callback runs after the component finishes rendering — the perfect place for an API call.
import { useEffect } from "react"; useEffect(() => { fetchData(); // runs AFTER render }, []); // dependency array
- Order: the component renders first, then the effect's callback fires. (Log inside both and "body rendered" prints before "useEffect called.")
- The empty
[]means "run once" — after the first render only. (More on dependencies in a later session.)
Fetching data: fetch + async/await#
Fetching in React is plain JavaScript — fetch() is a browser superpower, not a React one. Resolve its promise with async/await (cleaner than .then() chains).
const fetchData = async () => { const data = await fetch(SWIGGY_URL); const json = await data.json(); setListOfRes(json?.data?.cards…); // drop into state → re-render };
- Update state with the result. Calling the setter re-renders the component with live data — the same state-driven flow shown earlier.
- Real APIs are deeply nested & messy — you dig through paths like
json.data.cards[2].data.data.cards. Use optional chaining (?.) to read it safely.
CORS — why the call gets blocked (and how to fix it)#
Calling another site's API from localhost often fails with a CORS error. It's the browser blocking requests from one origin to a different origin — a security policy, not a React bug.
CORS is a bouncer at the API's door. The server decides which origins are on the guest list. Your localhost isn't on Swiggy's list, so the browser's bouncer turns the request away.
CORS browser extension
Toggles the check off in your browser. Fine for local learning — but your users won't have it, so it can't ship.
A CORS proxy
Route the call through a proxy (e.g. corsproxy.io) by prefixing your API URL. The proxy fetches from Swiggy server-side and returns it — no browser block.
- Why the proxy works: the browser only blocks browser cross-origin calls. A server has no such limit, so the proxy calls the API for you and hands back the data.
- Real production fix: the API's own server enables CORS for your origin, or your own backend proxies the request. Public proxies are rate-limited (~tens of calls/min) — fine for dev, not for scale.
Shimmer UI & conditional rendering#
While the API loads, don't show a blank page or a plain spinner — show a shimmer UI: fake skeleton cards that resemble the real layout. It's the modern standard (Swiggy, YouTube) and feels faster.
// conditional rendering with a ternary return listOfRes.length === 0 ? <Shimmer /> : <div className="res-container">…</div>;
- Conditional rendering = rendering different JSX based on a condition. The
if/elseor a ternary (? :) is just JavaScript — the fancy interview name is the only new part. - Empty list = data not here yet —
listOfRes.length === 0is the signal to show the shimmer.
Controlled input: why typing "does nothing"#
A search box bound to state via value={searchText} is a controlled component — React owns its value. If you only set value and never update the state, typing appears frozen, because the box always shows the (empty) state.
<input
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>- The fix is
onChange: on every keystroke, reade.target.valueand update state. State → re-render → the box shows the new value. That two-way wiring is what makes a controlled input work. - Every keystroke re-renders the component — and that's fine. React's diffing means only the input's value actually changes in the real DOM, so it stays fast even after dozens of renders.
Search & the "filter the original" trap#
On search, filter the data and update state — React re-renders with the matches. But filtering and overwriting your one list destroys the full data, so the next search has nothing to search.
- Keep two state variables: the full
listOfRes(never mutated after fetch) and afilteredResyou render. Filter from the full list into the filtered one — so every search starts from all results. - Make search case-insensitive — lowercase both sides and use
.includes()(not===) so "coffee" matches "Third Wave Coffee."
When the live API changes — find it in DevTools#
Live APIs change over time. The durable skill isn't memorizing one data path — it's reading the API off the real site instead of guessing when it breaks or you don't know its URL.
Open DevTools → Network tab
Filter to API calls only
Find the call returning the data
Use it in your fetch
- Install a JSON viewer extension to read big responses; open the API URL in a tab and use it to explore/search the structure.
Trace the nested JSON#
The response is one huge config-driven blob — offers, "what's on your mind," filters, and (somewhere) the restaurant list. Walk the tree to the part you actually need.
// the restaurants live deep inside; the exact path shifts over time const restaurants = json?.data?.cards[5]?.card?.card ?.gridElements?.infoWithStyle?.restaurants;
- Config-driven UI, revisited: each top-level card builds one section (offers, filters, headings). You only pluck the card holding restaurants — which card index that is can change.
- Always use optional chaining (
?.) so a missing intermediate doesn't crash the whole app.
Re-map the renamed fields#
When the shape changes, the fix is small — update the keys your card reads. In this change, the per-restaurant wrapper key data became info, and a couple of fields moved/changed type.
Wrapper renamed
restaurant.data.id → restaurant.info.id (same for name, image, rating…).
Number → string
It's now a ready string ("₹350 for two") — stop dividing by 100, just print it.
Moved & renamed
Now info.sla.slaString (e.g. "22 mins") instead of a top-level deliveryTime.
- Debug by reading the error + logging the object. "Cannot read properties of undefined (id)" points straight at the renamed path; log the restaurant to see the real keys.
Lazy loading via POST#
The first call returns only ~9 restaurants. Scrolling triggers a separate POST API (passing lat/long) to load more — this is lazy loading / pagination.
- GET vs POST: a GET puts params in the URL; a POST sends a body.
fetch(url, { method: "POST", body … })for the "load more" call.
Gotchas & nice-to-knows#
CORS blocks cross-origin calls
The browser blocks localhost → other-site API. A dev extension unblocks it locally; for everyone, route through a CORS proxy (or fix it server-side).
Frozen input box
Setting value without onChange makes typing do nothing — it's a controlled input with no way to update.
Search === vs includes
Use .includes() for partial matches and lowercase both sides for case-insensitivity.
Don't overwrite the source list
Filtering into the same list loses your data. Keep a separate filtered list.
Renamed API keys crash render
"Cannot read properties of undefined" usually means a field moved (e.g. data → info). Log the object and re-map the path.
Shimmer > spinner
Skeleton cards set expectations and feel faster than a lone spinner.
Two renders on load
The page renders once empty, then again after the fetch updates state — expected, not a bug.
APIs change — that's normal
Backend teams reshape responses; frontend adapts. Find the call in DevTools and trace the new JSON.
Interview Q&A#
Q.What is useEffect and when does it run?
[], the callback runs once, right after the first render.Q.How do you fetch data in React, and where?
fetch() (a browser feature) with async/await, inside a useEffect so it runs after render. Put the result into state with the setter — that triggers a re-render showing the live data.Q.What is CORS?
Q.How do you get past CORS without a browser extension?
corsproxy.io): prefix your API URL with the proxy's. The browser only blocks browser cross-origin calls, so the proxy — a server — fetches the API and returns the data. Unlike an extension, this works for all your users. Public proxies are rate-limited, so production should use the API's own CORS config or your own backend proxy.Q.What is a controlled component?
value={state}) and updated via onChange. React is the single source of truth for what's in the box — you read and set it through state.Q.What is conditional rendering?
if, a ternary (? :), or && . It's just JavaScript deciding what JSX to return.Q.What is shimmer UI and why use it?
Q.Why does the component re-render on every keystroke?
Q.An API you depend on changed its response. How do you fix it?
Q.Why use optional chaining when reading API data?
a?.b?.c returns undefined instead of throwing if b is missing — so one absent field doesn't break the whole render.Q.GET vs POST for fetching?
fetch, POST needs an options object with method and body.