Study Index
React ·Foundations Refresher ·Part II

React, finding the path

The notes continue —from a single page into a real multi-page app: routing, dynamic URLs, and the hook rules that keep state predictable. Basics kept brief, gotchas surfaced, interview Q &A in every section.

Session 07

Finding the Path

Many URLs, one page that never reloads —routing, dynamic pages, and the rules hooks live by.

A React app stays a single page . Routing doesn't fetch new HTML —it just swaps components in and out as the URL changes, so navigation is instant and the shell (header, footer) never reloads.

7.1

useEffect, deeper: the three timing cases

The same hook from before, but the second argument (the dependency array) is what decides when the effect runs. There are exactly three cases, and interviewers love this one.

No array
After every render

Leave the array out entirely and the effect runs after each render —usually not what you want.

Empty array []
Once, on first render

Runs a single time after the initial render. This is where data fetching belongs.

[someValue]
When that value changes

Runs on first render, then again only when a listed value changes (e.g. search text).

                        // 1 - runs after every render
                        useEffect(() => { ... });

// 2 - runs once, after the first render
                        useEffect(() => { ... }, []);

// 3 - runs whenever btnName changes
                        useEffect(() => { ... }, [btnName]);
                    
  • The callback is required; the array is optional. Adding the array is what changes the timing —it's a filter on "when should this re-run."
  • Why fetch with [] : you want the API call once, not on every keystroke or click. An empty array says "set this up after the component first appears, then leave it alone."
7.2

useState, the rules: call hooks at the top, never inside conditions

State variables belong inside a component function, declared at the very top —before any logic. Putting a hook inside an if , a loop, or a nested function breaks React.

  • Inside the component only: declaring state outside the function throws "Invalid hook call —hooks can only be called inside the body of a function component." State is local to a component, so it has to live there.
  • At the top: declare all your useState / useEffect calls first, before branching or returning. This keeps their order identical on every render.
  • Never in if /for /nested functions: a hook inside a condition might run on some renders and skip on others. React tracks hooks by call order , so a changing order corrupts which state belongs to which variable.
Analogy ·recall

Think of hooks like numbered seats on every render . React hands out state by seat order: seat 1, seat 2, seat 3. If a hook hides behind an if , the seats shift on the next render —and React hands the wrong state to the wrong variable. Keep them at the top, always in the same order, and the seating never changes.

7.3

Routing needs a library: React Router DOM

React itself doesn't do URLs —it renders components. To map URLs to components you add a routing library; the standard choice is React Router DOM, installed like any package.

                        // install it like any npm package
                        npm install react-router-dom
                    
  • It's just another dependency: installing adds it to package.json and package-lock.json , same as anything else.
  • Version matters: the v6 API (used here) is different from v5 —older tutorials and projects may show the v5 style, so check the version before copying code.
7.4

The routing config: createBrowserRouter + RouterProvider

You describe your routes as a plain list of objects —each says "for this path, render this element." Then you hand that config to a provider instead of rendering a single component directly.

                        import
                        { createBrowserRouter, RouterProvider }
  from
                        "react-router-dom"
                        ;

const
                        appRouter = createBrowserRouter
                        ([
  { path: "/"
                        ,       element: <AppLayout />},
  { path: "/about"
                        ,  element: <About />},
  { path: "/contact"
                        , element: <Contact />},
]);

// instead of rendering <AppLayout/>directly:
                        root.render
                        (<RouterProvider router={appRouter} />);
                    
createBrowserRouter
Builds the config

Takes an array of route objects (each a path + element ) and returns a router.

RouterProvider
Wires it in

A component that takes your router via the router prop and actually renders the matched route.

  • The config is data, not code branches: a list of { path, element } objects —React Router reads it and decides what to show for the current URL.
  • Other router types exist (createHashRouter , memory, static) for special cases, but createBrowserRouter is the recommended default for web apps.
7.5

Custom error pages: errorElement + useRouteError

Hit an unknown URL and React Router shows its own error screen. You can replace it with your own by adding an errorElement , and read what went wrong with the useRouteError hook.

                        { path: "/"
                        , element: <AppLayout />,
  errorElement: <Error />}

// inside the Error component:
                        const
                        err = useRouteError
                        ();
return
                        <h3 >{err.status}: {err.statusText}</h3 >;
                    
  • errorElement renders whenever a route throws or the URL doesn't match —so users see a friendly page, not React's raw red crash screen.
  • useRouteError() hands you an error object (status , statusText , etc.) so you can show "404 —Not Found" instead of a generic message.
  • Spot a hook by its name: anything starting with use is a hook —a function with a special purpose, by convention.
7.6

Nested routes &the <Outlet >

You usually want the header and footer to stay put while only the middle swaps per page. You do that by nesting routes as children of a layout, and marking where the child should appear with <Outlet /> .

                        const
                        appRouter = createBrowserRouter
                        ([
  { path: "/"
                        , element: <AppLayout />,
    children: [
      { path: "/"
                        ,        element: <Body />},
      { path: "/about"
                        ,   element: <About />},
      { path: "/contact"
                        , element: <Contact />},
    ],
  },
]);

// AppLayout renders the shell + an outlet
                        const
                        AppLayout = () => (
  <div ><Header /><Outlet />{/* child route renders here */}
                        </div >);
                    
Analogy ·recall

<Outlet /> is a picture frame built into your layout. The frame stays on the wall (header above, footer below); React Router just slides a different photo into it depending on the URL. The frame itself never shows up in the final HTML —it's replaced by whichever child matched.

  • The parent stays mounted: the layout (and its header) renders once; only the <Outlet /> contents change per route. That's how the header survives navigation.
  • You won't find <Outlet > in the DOM: it's a placeholder —the matched child component takes its place in the rendered HTML.
7.7

<Link >vs <a >: don't reload the whole page

An anchor tag works, but it reloads the entire app on every click —slow, and it throws away your loaded state. Inside React, navigate with <Link > instead.

<Link to >

From React Router DOM. Swaps components client-side —no full reload , header stays, navigation is instant. Use this for in-app navigation.

vs
<a href >

The browser's anchor. Triggers a full page reload —refetches everything, logo flickers, state resets. Fine for external links, wrong for in-app routes.

  • to instead of href : the only syntax difference. <Link to="/about">About </Link > .
  • Under the hood, <Link > still renders an <a > —the browser only understands anchors. React Router wraps it, intercepts the click, and updates the view without a reload.
7.8

SPA: client-side vs server-side routing

Because <Link > only swaps components, the app is a single page application —loaded once, then components interchange. That's client-side routing, and it's different from the old way.

Client-side routing

All page components ship on first load. Navigating just renders a different component —no network call for the page . Calls happen only when you fetch data.

vs
Server-side routing

The old model: about.html , contact.html . Each click fetches a fresh HTML file from the server and reloads the whole page.

  • Don't confuse server-side routing with server-side rendering : routing is "where do pages come from on navigation"; rendering (SSR) is "where the HTML is first generated." Different topics.
7.9

Dynamic routes: one component, many URLs

You don't write a route per restaurant. You write one route with a variable segment —marked with a colon —and one component that loads whatever id is in the URL.

                        { path: "/restaurants/:resId"
                        ,
  element: <RestaurantMenu />}

// /restaurants/425  -> resId = "425"
                        // /restaurants/229  -> resId = "229"
                    
  • :resId is a placeholder: the colon tells React Router this part of the path is dynamic. Any value slots in and the same <RestaurantMenu /> renders.
  • One reusable component, dynamic data: the markup is identical for every restaurant —only the data loaded inside it changes. That's reusability.
7.10

Reading the URL param: useParams

To fetch the right data, the component needs the id from the URL. The useParams hook gives you an object of the dynamic segments; destructure the one you named.

                        import
                        { useParams } from
                        "react-router-dom"
                        ;

const
                        { resId } = useParams
                        ();

useEffect(() => { fetchMenu
                        (); }, []);

const
                        fetchMenu = async
                        () => {
  const
                        data = await
                        fetch
                        (MENU_API + resId);
  const
                        json = await
                        data.json
                        ();
  setResInfo
                        (json.data);
};
                    
  • The key matches the route: you named it :resId in the path, so useParams() returns { resId } . Destructure it on the fly.
  • Build the API URL from the param: MENU_API + resId —same component, different restaurant, just by changing the id in the URL.
  • Keep API URLs in a constants file rather than hard-coded in the component —cleaner and reusable.
7.11

Rendering the menu: state, shimmer, early return, map + keys

Fetch into state, show a shimmer until it arrives, then render the list. Two traps appear here —reading into null , and rendering a list without keys.

                        const
                        [resInfo, setResInfo] = useState
                        (null
                        );

// early return - guards against null before render
                        if
                        (resInfo === null
                        ) return
                        <Shimmer />;

const
                        { name, cuisines } = resInfo?.cards[0]?.card?.card?.info;
const
                        itemCards =
  resInfo?.cards[2]?.groupedCard?.cardGroupMap
    ?.REGULAR?.cards[1]?.card?.card?.itemCards;

return
                        (
  <ul >{itemCards.map
                        ((item) =><li key={item.card.info.id}>{item.card.info.name}
      </li >)}
  </ul >);
                    
  • Early return beats a ternary here: if resInfo is null , return the shimmer before any code tries to read .name off null. A ternary in the JSX still runs the destructuring above it —and crashes.
  • Always pass key in a .map() : without it React warns "each child in a list should have a unique key." Use a stable id, never the array index.
  • Optional chaining everywhere in deep data: live JSON is deeply nested and shifts over time —?. stops one missing field from crashing the whole render.
  • Live APIs change: the exact card indices and field names drift. Don't panic —open the network tab, find the current API and shape, and adjust the path. The knowledge transfers even when the data doesn't.
7.12

Gotchas &nice-to-knows

Gotcha
Hook inside a condition

Calling useState /useEffect inside an if , loop, or nested function corrupts hook order. Always call them at the top level.

Gotcha
Anchor tag reloads everything

Using <a > for in-app links does a full page reload and resets state. Use <Link > instead.

Gotcha
Reading data off null

Destructuring before the fetch returns crashes. Guard with an early return (if (x === null) return <Shimmer/> ).

Gotcha
Missing key in map

Lists without a unique key trigger a React warning and can cause wrong re-renders. The key goes on the outermost mapped element.

Nice to know
Link is an anchor underneath

In the DOM, <Link > renders as <a > —React Router just intercepts the click to avoid a reload.

Nice to know
GraphQL fights over-fetching

This menu API returns far more than the UI uses. GraphQL lets the client ask for exactly the fields it needs —less over/under-fetching.

7.13

Interview Q &A

Q. When does useEffect run, and how does the dependency array change that?
By default it runs after every render. With an empty array [] it runs once, after the first render. With values inside [a, b] it runs on the first render and again whenever one of those values changes. The array is a filter on when to re-run.
Q. Why must hooks be called at the top level, never inside conditions or loops?
React matches state to hooks by their call order on each render. If a hook sits inside an if or loop, that order can change between renders, and React hands the wrong state to the wrong variable. Calling them all at the top keeps the order stable.
Q. What is a single page application (SPA)?
An app that loads one HTML page once, then swaps components in and out as you navigate —without fetching new pages. React Router does this with client-side routing, so moving between pages feels instant and never reloads the whole app.
Q. Difference between client-side and server-side routing?
Client-side routing keeps all page components in the app and just renders a different one on navigation —no page network call. Server-side routing fetches a separate HTML file from the server for each URL and reloads the page. SPAs use client-side routing.
Q. Why use <Link >instead of an anchor tag in React?
An anchor reloads the entire page, which is slow and resets your app's state. <Link > navigates client-side —it swaps components without a reload, so the header stays and it's instant. It renders as an <a > underneath, but React Router intercepts the click.
Q. How do you build a dynamic route and read its parameter?
Add a colon segment to the path, like /restaurants/:resId , and point it at one component. Inside that component, call useParams() —it returns an object with the named params —and destructure resId to fetch the right data.
Q. What does <Outlet >do?
It marks the spot in a parent layout where the matching child route should render. The layout (header, footer) stays mounted, and only the <Outlet /> contents change as the URL changes. It doesn't appear in the final DOM —the child replaces it.
Q. How do you handle a route error or a 404 in React Router?
Add an errorElement to the route —it renders whenever the route throws or the URL doesn't match. Inside that component, call useRouteError() to read the error's status and statusText so you can show a real message like "404 —Not Found."
7.14

One-line recall

no array = every render [] = once [dep] = on change hooks at the top never hooks in if/loop react-router-dom createBrowserRouter RouterProvider router= path + element errorElement useRouteError() children + <Outlet/> <Link to >not <a href > no reload = SPA client vs server routing :resId dynamic segment useParams() early return for null map needs a key optional chaining ?.
Session 08

Let's Get Classy

The old way of writing components —and the lifecycle it reveals about how React actually mounts, updates, and cleans up.

Class components are the older way to write React —you rarely build with them now. But they expose the lifecycle hooks were built to replace, and that's the real prize: understanding when a component mounts, updates, and unmounts.

8.1

Why learn class components at all?

You won't reach for them in new code —functional components with hooks are the modern standard. Three reasons to know them anyway.

Interviews
Asked a lot

Many companies maintain older codebases built on class components, so interviewers test them.

Legacy code
Still out there

You'll meet class components in existing projects and need to read and edit them.

Deeper model
The lifecycle

They make React's mount/update/unmount phases explicit —which sharpens how you understand hooks.

8.2

The syntax: a class with a render method

A functional component is a function that returns JSX. A class component is a class that extends React.Component and has a render() method returning JSX. That's the core difference.

                        import
                        React from
                        "react"
                        ;

// functional: a function returning JSX
                        const
                        User = () => return
                        <div >...</div >;

// class: extends React.Component, render() returns JSX
                        class
                        UserClass extends
                        React.Component {
  render
                        () {
    return
                        <div >...</div >;
  }
}
                    
  • extends React.Component is what tells React "this is a class component" so it tracks and manages it. React.Component is a base class you inherit from, imported from react .
  • Import styles vary: you'll see extends React.Component or, with a named import, extends Component . Same thing —don't let the two forms confuse you.
  • Import/export is identical to functional components —nothing special needed to use a class component once it's defined.
8.3

Props in a class: constructor + super(props)

Passing props is the same. Receiving them differs: a class takes them in its constructor , calls super(props) , and then reads them anywhere via this.props .

                        // passing - same as functional
                        <UserClass name="Akshay"
                        location="Dehradun"
                        />class
                        UserClass extends
                        React.Component {
  constructor
                        (props) {
    super
                        (props);
  }
  render
                        () {
    const
                        { name, location } = this
                        .props;
    return
                        <h2 >{name} —{location}</h2 >;
  }
}
                    
  • All props arrive as one object: everything you pass is bundled into a single props object handed to the constructor.
  • Read with this.props : use this.props.name anywhere in the class, or destructure for cleaner code —the same on-the-fly destructuring you use in functional components.
  • Why super(props) : you're extending a base class, so you must call its constructor first —and passing props up is what makes this.props available inside the constructor. (Classic interview "why" question.)
8.4

State in a class: this.state &this.setState

Where functional components use useState , a class keeps all its state in one object, this.state , set up in the constructor. You update it only through this.setState —never by assignment.

                        constructor
                        (props) {
  super
                        (props);
  this
                        .state = {
    count: 0
                        ,
    count2: 2
                        ,   // one object holds every state variable
                        };
}

// update - NEVER this.state.count = ... directly
                        this
                        .setState
                        ({ count: this
                        .state.count + 1
                        });
                    
  • One object, many variables: unlike multiple useState calls, a class groups all state into the single this.state object. (Behind the scenes, React keeps functional state as one object too.)
  • Never mutate directly: this.state.count = 5 won't re-render and creates inconsistencies —the class equivalent of the "don't reassign state" rule. Always go through setState .
  • setState merges, not replaces: pass only the keys you're changing and React leaves the rest untouched. It diffs, updates those variables, and re-renders. You can batch several keys in one call.
8.5

The mounting lifecycle: constructor →render →componentDidMount

When a class component loads (we say it mounts —an instance of the class is created), three methods run in a fixed order.

1 · constructor

Set up state &props

2 · render

Return JSX

· DOM update ·

React paints to the page

3 · componentDidMount

Runs after it's on screen

  • Mounting = creating an instance: "loading a class component" means a new instance of the class is created, which is why the constructor runs first (constructors run on instantiation, not invocation).
  • componentDidMount runs last, only after React has actually put the component on the DOM —hence the name. That timing is the whole point of the next section.
8.6

Render phase vs commit phase (and why React is fast)

React splits mounting into two phases. This is the key to the trickiest interview question: the order of lifecycle calls when a parent has multiple children.

Render phase

Runs constructor + render . Pure JavaScript on the virtual DOM —computes what changed (the diff). Fast, and React batches this across siblings.

then
Commit phase

React actually updates the real DOM, then fires componentDidMount . DOM manipulation is expensive, so React batches all commits together.

                        // parent with two class children, the real order:
                        parent constructor
parent render
  first child constructor
  first child render      // render phase batched
                        second child constructor
  second child render
  first child componentDidMount   // commit phase batched
                        second child componentDidMount
parent componentDidMount
                    
  • The surprise: the first child's componentDidMount does not fire right after its render. React runs the render phase for all children first, then the commit phase for all of them.
  • Why batch: updating the real DOM is the slowest part. By computing all the diffs first (cheap, in-memory virtual DOM) and committing once, React minimizes expensive DOM work —that's a big reason it's fast.
Analogy ·recall

Think of a print shop . The render phase is laying out every page on screen —quick, all done together. The commit phase is the actual printing —slow and expensive, so you batch every page into one print run instead of walking to the printer after each page. React lays out all children first (render), then prints them all at once (commit).

8.7

componentDidMount = where API calls go

In a class, you fetch data in componentDidMount —the same role useEffect(fn, []) plays in functional components. The reason is the two-phase timing.

                        async
                        componentDidMount
                        () {
  const
                        data = await
                        fetch
                        (GITHUB_API);
  const
                        json = await
                        data.json
                        ();
  this
                        .setState
                        ({ userInfo: json });
}
                    
  • Render fast, then fill: the component mounts immediately with default/empty state (so the user sees something), then the API call runs and setState re-renders with real data. React doesn't wait on the network to paint.
  • Why after mount: because componentDidMount fires once the DOM is already on screen —the ideal moment to kick off a fetch without blocking the first paint.
8.8

The update cycle: setState →render →componentDidUpdate

After mounting, any setState starts an update cycle: React re-runs render (not the constructor —that runs once), updates the DOM, then calls componentDidUpdate .

                        componentDidUpdate
                        (prevProps, prevState) {
  // the old pain: manual guards to avoid infinite loops
                        if
                        (this
                        .state.count !== prevState.count) {
    // run effect for count change
                        }
}
                    
  • Constructor runs once; render runs every update. Mounting fills state with defaults; each setState re-renders with the new values and then fires componentDidUpdate .
  • This is where hooks shine: the messy prevProps /prevState comparisons (one per variable, easy to get wrong) collapse into one dependency array —useEffect(fn, [count]) . That's the pain the array was designed to remove.
8.9

componentWillUnmount: clean up your mess

Just before a component leaves the screen, componentWillUnmount runs. In a single-page app the page never reloads, so anything you started —timers, subscriptions —keeps running unless you stop it here.

                        componentDidMount
                        () {
  this
                        .timer = setInterval
                        (() => {
    console.log
                        ("still running…"
                        );
  }, 1000
                        );
}

componentWillUnmount
                        () {
  clearInterval
                        (this
                        .timer);  // stop the leak
                        }
                    
  • The SPA leak: a setInterval started in componentDidMount keeps firing after you navigate away —and starts a new one each time you return. Several stale timers pile up invisibly and degrade performance.
  • The fix: store the timer id on this (shared across all the class's methods) in componentDidMount , and clearInterval it in componentWillUnmount . Create a mess, clean the mess.
  • Hooks equivalent: return a cleanup function from useEffect —it runs on unmount, doing the same job as componentWillUnmount .
                        // functional cleanup - the modern equivalent
                        useEffect(() => {
  const
                        timer = setInterval
                        (..., 1000
                        );
  return
                        () => clearInterval
                        (timer); // runs on unmount
                        }, []);
                    
8.10

Gotchas &nice-to-knows

Gotcha
Mutating state directly

this.state.count = 5 won't re-render. Always use this.setState({...}) .

Gotcha
Forgetting super(props)

Skip it and this.props is undefined in the constructor. Always call super(props) first.

Gotcha
Uncleaned timers in an SPA

Intervals/subscriptions outlive the component and stack up. Clear them in componentWillUnmount (or a useEffect cleanup).

Gotcha
Expecting didMount per-child immediately

With multiple children, all renders fire before any componentDidMount —render phase is batched.

Nice to know
Don't equate useEffect &didMount

They overlap in use, but useEffect is not "componentDidMount under the hood" —it's a different mechanism. Don't memorize them as equal.

Nice to know
this is shared across methods

Anything on this (like this.timer ) is reachable from every method of the class.

8.11

Interview Q &A

Q. What's the difference between a functional and a class component?
A functional component is a function that returns JSX. A class component extends React.Component and returns its JSX from a render() method. Functional components with hooks are the modern standard; class components are the older style still found in legacy code.
Q. How do you read props and state in a class component?
Props arrive in the constructor; call super(props) and then read them anywhere via this.props . State lives in one object, this.state , set in the constructor, and you read it via this.state.x . You can destructure both for cleaner code.
Q. Why must you use setState instead of assigning to this.state?
Assigning directly (this.state.count = 5 ) doesn't tell React anything, so nothing re-renders and you get inconsistent state. setState updates the state, diffs it, and triggers a re-render. It also merges —it only changes the keys you pass.
Q. What is the order of lifecycle methods on mount?
Constructor first, then render, then React updates the DOM, then componentDidMount . Constructor and render are the render phase; the DOM update and componentDidMount are the commit phase.
Q. With a parent and multiple children, why doesn't each child's componentDidMount fire right after its render?
React batches the render phase across siblings: it runs every child's constructor and render first, then runs every child's componentDidMount in the commit phase. It does this because updating the real DOM is expensive, so it computes all changes first and commits them together.
Q. Why are API calls made in componentDidMount?
So the component renders quickly first (with empty or placeholder state), then fetches and calls setState to fill in the data. Since componentDidMount runs after the DOM is painted, it's the right moment to start a fetch without blocking the first render.
Q. What is componentWillUnmount for?
Cleanup just before a component leaves the screen. In a single-page app the page doesn't reload, so timers, intervals, or subscriptions keep running and stack up. You clear them here —e.g. clearInterval(this.timer) . In functional components, the cleanup function returned from useEffect does the same job.
Q. How do these class lifecycle methods map to useEffect?
Roughly: [] covers the "run once after mount" role, a populated dependency array covers "run on change," and a returned cleanup function covers unmount. But don't claim they're identical —useEffect is its own mechanism, not class lifecycle methods in disguise.
8.12

One-line recall

class extends React.Component render() returns JSX constructor(props) + super(props) this.props this.state = one object this.setState() merges never mutate state directly constructor →render →didMount render phase vs commit phase render phase batched commit = DOM update (expensive) API call in componentDidMount setState →render →didUpdate constructor runs once componentWillUnmount = cleanup clearInterval on unmount useEffect cleanup = return fn this is shared across methods don't equate useEffect = didMount
Session 09

Optimizing the App

Cleaner code and a faster app —custom hooks for modularity, code splitting so the bundle stays light.

Two levers separate a junior from a senior build: give each piece one job (custom hooks pull logic out of components), and stop shipping one giant bundlesplit the code so each route loads only when needed.

9.1

Single Responsibility &modularity

Each component (it's just a function) should do one thing well. If a component is doing several jobs, break it into smaller pieces —that's modularity.

  • One job per component: a RestaurantCard takes props and renders a card —nothing more. A reader should grasp it at a glance.
  • Why it pays off: small, focused pieces are reusable (drop the card anywhere), maintainable (find code fast), and testable (write a test per unit, so a bug is caught in the small piece, not hunted across a giant component).
  • No hard rule: there's no exact line for "too much" —the aim is keeping components light and readable for the next person.
9.2

Custom hooks: a hook is just a function

A hook is a normal JavaScript helper function with React powers (it can hold state, run effects). You've used library hooks (useState , useEffect , useParams ) —you can write your own the same way.

  • Don't panic at the word "hook": it's a utility function. Your custom hook is no different from useParams —someone just wrote logic inside it and returned a value.
  • The point: move a chunk of logic out of a component and into a hook, so the component gets lighter and the logic becomes reusable.
9.3

Building useRestaurantMenu

The menu component had two jobs: fetch the data and display it. Pull the fetching into a hook so the component is left with one job —displaying.

                        // utils/useRestaurantMenu.js
                        import
                        { useState, useEffect } from
                        "react"
                        ;
import
                        { MENU_API } from
                        "../utils/constants"
                        ;

const
                        useRestaurantMenu = (resId) => {
  const
                        [resInfo, setResInfo] = useState
                        (null
                        );

  useEffect
                        (() => { fetchData
                        (); }, []);

  const
                        fetchData = async
                        () => {
    const
                        data = await
                        fetch
                        (MENU_API + resId);
    const
                        json = await
                        data.json
                        ();
    setResInfo
                        (json.data);
  };

  return
                        resInfo;        // the contract: in resId, out resInfo
                        };
export default
                        useRestaurantMenu;
                    
                        // the component is now clean - one job: display
                        const
                        resInfo = useRestaurantMenu
                        (resId);
                    
  • Think in contracts: before writing a hook, fix its input and output. Here: in a restaurant id, out the restaurant info. The rest is implementation the caller never sees.
  • It can hold its own state: a hook uses useState /useEffect just like a component —React tracks it the same way.
  • Convention: one hook per file in a utils folder, named after the hook.
Analogy ·recall

A custom hook is like a vending machine . You put in a coin (the resId ) and a snack comes out (the resInfo ). You don't care about the motors and sensors inside —just the slot in and the tray out. The component only knows the contract, not the machinery.

9.4

Building useOnlineStatus

A second hook to cement the pattern: report whether the user is online. Its contract takes no input and returns a boolean —useful for online/offline dots, "you're offline" screens, or an offline mini-game.

                        const
                        useOnlineStatus = () => {
  const
                        [online, setOnline] = useState
                        (true
                        );

  useEffect
                        (() => {
    window.addEventListener
                        ("offline"
                        , () => setOnline
                        (false
                        ));
    window.addEventListener
                        ("online"
                        ,  () => setOnline
                        (true
                        ));
  }, []);

  return
                        online;
};
                    
                        // use it anywhere, in one line
                        const
                        online = useOnlineStatus
                        ();
return
                        online ? <Cards />: <h1 >You're offline </h1 >;
                    
  • Browser event listeners: window fires online and offline events. Register them once inside a useEffect with [] , flip the state, return it.
  • Reusability in action: the same one-line hook powers the offline screen in the body and a green/red status dot in the header —write once, use anywhere.
  • Tip: test offline behavior with the browser's Network tab "Offline" / throttling toggle, not by killing your Wi-Fi.
9.5

The use prefix &why it matters

Start every hook's name with use (lowercase). It isn't strictly required, but it's strongly recommended —and most projects' linters enforce it.

  • Signals "this is a hook": the use prefix tells React (and your teammates) the function follows hook rules and may hold state/effects —not a plain helper like getOnlineStatus .
  • Linters enforce it: a React-configured ESLint will error if you skip use . Follow the convention the library recommends rather than fighting it.
9.6

The bundle problem: one huge JS file

A bundler (Parcel here) takes all your files and bundles them into one JavaScript file the browser loads. Fine for a small app —but as components grow into the thousands, that single file balloons and slows the first load.

  • One file ships everything: home, about, contact, every route's code —all in one bundle, even though the user may never visit most of it.
  • Neither extreme works: one giant file is too heavy to download; thousands of separate files means too many browser requests. The answer is something in between.
9.7

Code splitting: many names, one idea

Break the app into smaller bundles loaded on demand. You'll hear this called many things —they all mean the same thing.

code splitting chunking lazy loading on-demand loading dynamic import dynamic bundling
  • Split along features: bundle by logical area —e.g. a food-delivery chunk and a separate grocery chunk —so each is a small app inside the big app.
  • Why it's a senior-level topic: in front-end system-design interviews, knowing when and how to split bundles is exactly what signals you can build large, performant apps.
9.8

lazy() + Suspense in practice

Replace a normal import with React's lazy , which takes a function that dynamically import s the component. React then loads that code only when the route is visited —and you wrap it in Suspense to show a fallback while it loads.

                        import
                        React, { lazy, Suspense } from
                        "react"
                        ;

// not a normal import - loaded on demand
                        const
                        Grocery = lazy
                        (() => import
                        ("./components/Grocery"
                        ));

// wrap it so React has something to show while it loads
                        { path: "/grocery"
                        ,
  element: (
    <Suspense fallback={<h1 >Loading…</h1 >}><Grocery /></Suspense >) }
                    
  • What it does: the main bundle no longer contains the grocery code. Visiting /grocery triggers a separate grocery.js request —the code arrives only on demand.
  • Why Suspense is required: while the chunk is downloading, the component isn't there yet. React "suspends" rendering and throws an error unless you provide a Suspense boundary with a fallback to show meanwhile (a "Loading…" message or a shimmer).
  • The win: a few lines keep the initial bundle small, so a large app loads fast. Lower bundle size is the whole goal —this is how you fight app "bloat."
9.9

Gotchas &nice-to-knows

Gotcha
lazy import without Suspense

A lazy component with no Suspense wrapper throws "a component suspended while responding to synchronous input." Always wrap it.

Gotcha
Wrong JSON path in the hook

Moving fetch logic into a hook still needs the right shape —e.g. return json.data , not json , or you get undefined.

Gotcha
Forgetting the use prefix

Skipping use works at runtime but trips React-configured linters and hides that it's a hook.

Nice to know
import() is a function

Inside lazy , import("./X") is the dynamic-import function (returns a promise) —not the static import statement.

Nice to know
The Chrome dino game

The offline game is just online-status detection plus a fallback UI —the exact pattern useOnlineStatus enables.

Nice to know
Split by feature, not file

Good chunks map to features (flights, hotels, grocery), each holding enough code to stand alone.

9.10

Interview Q &A

Q. What is the Single Responsibility Principle in React?
Each component (or function) should do one job. A card just renders a card; a menu just displays the menu. Keeping pieces focused makes your code reusable, easier to maintain, and easier to test.
Q. What is a custom hook and why use one?
It's a normal function, named with a use prefix, that can hold state and effects. You write one to pull logic (like data fetching) out of a component —so the component stays focused on display and the logic becomes reusable and testable.
Q. How do you start writing a custom hook?
Define its contract first: what's the input and what's the output. For useRestaurantMenu the input is a restaurant id and the output is the restaurant info; for useOnlineStatus there's no input and the output is a boolean. Once the contract is clear, the implementation follows.
Q. Why prefix a hook's name with "use"?
It tells React and other developers the function follows the rules of hooks and may use state or effects. It's strongly recommended —not strictly required —and most projects' linters will error if you skip it.
Q. What problem does code splitting solve?
A bundler ships your whole app as one JavaScript file. As the app grows, that file gets huge and slows the first load. Code splitting breaks it into smaller bundles loaded on demand, so users only download the code for the page they're on.
Q. Name some other terms for code splitting.
Chunking, lazy loading, on-demand loading, dynamic import, and dynamic bundling. They all describe the same thing: breaking the app into smaller bundles that load when needed.
Q. How do you lazy-load a component in React?
Import it with lazy(() => import("./Component")) instead of a normal import. React then loads that code only when the component is needed, producing a separate bundle.
Q. Why do you need Suspense with lazy?
While the lazily-loaded code is downloading, the component isn't available yet, so React suspends rendering and errors out. Wrapping it in Suspense with a fallback gives React something to show meanwhile, like a loading message or a shimmer.
9.11

One-line recall

single responsibility modular = reusable + testable a hook is just a function define the contract first useRestaurantMenu(resId) →resInfo useOnlineStatus() →boolean window online/offline events one hook per file in utils use prefix (linters enforce) bundler = one big JS file big bundle = slow first load code splitting = chunking lazy + dynamic import on-demand loading wrap in <Suspense fallback > split by feature smaller bundles = performant
Session 10

Jo Dikhta Hai, Wo Bikta Hai

Making the app beautiful —the ways to style React, and going deep on Tailwind's utility-first approach.

There's no single "right" way to style React —plain CSS, preprocessors, styled-components, component libraries, or utility CSS. This guide picks Tailwind : instead of writing CSS in a separate file, you compose utility classes right in your JSX.

10.1

The ways to style a React app

Before picking one, know the landscape. Each approach styles the same UI —the differences are in ergonomics, scale, and team preference.

Plain CSS
Class + stylesheet

Write a class in JSX, define it in a .css file. Simple, but doesn't scale cleanly on big apps.

SASS / SCSS
CSS with superpowers

Adds variables, nesting, and more to CSS. Handy, but not the common choice in modern production React.

styled-components
CSS-in-JS

Write styles inside JS, scoped to a component. Popular at large companies (e.g. used at Uber).

Component libraries
Pre-built &styled

Material UI, Bootstrap, Chakra UI, Ant Design —import a ready-made <Button > that's already beautiful, no CSS needed.

Tailwind CSS
Utility-first

Compose tiny single-purpose classes in your markup. Trending, and the pick here.

No single winner
It's a choice

All do the same job. Try a few and form your own opinion —there's no universally "correct" answer.

10.2

What "utility-first" means

Tailwind's pitch: style your components without leaving your JSX. Rather than naming a class and writing its CSS elsewhere, you apply many small classes —each does one CSS thing —directly in the markup.

Normal CSS

Add class="logo" in JSX, switch to index.css , write .logo { width: 14rem; } . Two files, back and forth.

vs
Tailwind

Write className="w-56" right where you build the element. The styling lives with the markup —no file switching.

  • One class = one rule: flexdisplay:flex , w-56width:14rem , p-4padding:1rem . You compose the look from small pieces.
  • Same mental model as CSS: you still think "make this flex, add padding, set width" —you're just expressing it as class names instead of a stylesheet.
10.3

Setting up Tailwind with Parcel

Tailwind isn't React-specific —it works with any framework or plain HTML. Setup with Parcel is a few steps; follow the framework guide that matches your bundler.

1 · Install

npm i tailwindcss postcss

2 · Init

npx tailwindcss init

3 · Configure

.postcssrc + content paths

4 · Import

@tailwind in index.css

                        /* index.css - the only CSS you write */
                        @tailwind base;
@tailwind components;
@tailwind utilities;
                    
                        // tailwind.config.js - where Tailwind looks for classes
                        content: ["./src/**/*.{html,js,jsx,ts,tsx}"
                        ],
                    
  • PostCSS does the work: Tailwind runs on PostCSS (a tool that transforms CSS via JavaScript). The .postcssrc file tells your bundler to read Tailwind —you don't have to learn PostCSS separately.
  • The content array lists where your classes live so Tailwind knows which files to scan. List only the file types you actually use.
  • Those three @tailwind lines are the entire stylesheet —after adding them, you may never touch the CSS file again.
10.4

The class nomenclature

Tailwind's class names follow consistent shorthand. Learn the pattern once and most classes become predictable.

Spacing
m / p + side + size

m-4 margin, p-4 padding, px-4 left+right, py-2 top+bottom, mb-2 margin-bottom. Numbers map to rem (4 = 1rem).

Layout
flex utilities

flex , flex-wrap , justify-between , items-center —flexbox without the verbosity.

Sizing
w- / h-

w-56 sets width (14rem). Sizes come from a scale.

Color
bg- / text- + shade

bg-pink-100 , bg-gray-400 —shades run 50 →950; higher = bolder/darker.

Type &effects
font / text / shadow / rounded

font-bold , text-lg , shadow-lg , rounded-lg . Sizes use sm/md/lg/xl/2xl.

States
hover: prefix

hover:bg-gray-400 applies on hover. Same idea for focus, responsive breakpoints, etc.

10.5

Styling in practice &arbitrary values

You build a layout by stacking utilities. And when the scale doesn't have the exact value you need, escape to a custom one with square brackets.

                        // a header laid out with utilities
                        <div className="flex justify-between items-center shadow-lg"
                        ><img className="w-56"
                        /><ul className="flex"
                        ><li className="px-4"
                        >Home </li ></ul ></div >// arbitrary value when no class fits exactly
                        <div className="w-[200px]"
                        >...</div >
                    
  • Compose, don't author: a card is just m-4 p-4 w-[250px] rounded-lg shadow-lg —assembled from utilities, no separate CSS rule.
  • Arbitrary values: w-[200px] sets an exact size the scale doesn't offer. Use the built-in scale by default; reach for brackets only when you truly need a specific value.
  • You're not handicapped: hover states, complex layouts, even intricate components are all doable —there's a class for essentially everything.
10.6

The IntelliSense extension

The Tailwind CSS IntelliSense VS Code extension is what makes utility-first practical —you don't memorize classes, you get suggestions as you type.

  • Autocomplete: start typing pink or shadow and it lists matching classes —no trips to the Tailwind website.
  • Hover to inspect: hover any class to see the exact CSS it generates (e.g. w-56width: 14rem ).
  • If suggestions stall: trigger them manually with Ctrl +Space .
10.7

Pros, cons &why it stays lightweight

Tailwind is a tradeoff. The wins are speed and a tiny CSS bundle; the costs are a learning curve and busier markup.

Pro
No file switching

Style right in the JSX —faster iteration, no jumping between markup and stylesheets.

Pro
Tiny, purged bundle

The build includes only the classes you actually used —reuse m-4 100 times and it ships once. Unused classes (e.g. shadow) never reach the browser.

Con
Learning curve

Remembering the class names takes time up front; it gets fast once the patterns stick.

Con
Busy markup

Heavily-styled elements grow long className strings, which can make JSX harder to read.

Analogy ·recall

Utility classes are like LEGO bricks . Plain CSS is sculpting a custom piece in another room (the stylesheet); Tailwind hands you a bin of standard bricks you snap together right where you're building. Faster to assemble —but pour out enough bricks and the table (your markup) gets cluttered.

10.8

Gotchas &nice-to-knows

Gotcha
Classes outside content paths

If a file isn't in the content array, its Tailwind classes are purged out and won't apply. Keep the globs accurate.

Gotcha
Padding all sides vs one

p-4 pads every side. For a button you usually want px-4 py-2 —mixing axes, not all-around.

Gotcha
Full-width buttons

A block element stretches to its parent. Wrap it in a div (or set width) to size a button naturally.

Nice to know
Numbers map to rem

The spacing scale is rem-based: 4 = 1rem, 2 = 0.5rem (8px). Consistent across m/p/w/h.

Nice to know
Not React-only

Tailwind works with Angular, plain HTML, anything —it's a generic CSS framework.

Nice to know
Pre-built components exist

Tailwind UI offers ready-made component markup to copy —like a component library, but as utility classes.

10.9

Interview Q &A

Q. What are the main ways to style a React app?
Plain CSS files, preprocessors like SASS/SCSS, CSS-in-JS such as styled-components, component libraries like Material UI or Chakra, and utility-first frameworks like Tailwind. They all achieve the same result —the choice comes down to scale, ergonomics, and team preference.
Q. What is Tailwind CSS and how is it different from normal CSS?
It's a utility-first CSS framework: instead of writing rules in a separate stylesheet, you apply many small single-purpose classes directly in your markup. The thinking is the same as CSS —you just express the styles as class names without leaving your JSX.
Q. What is PostCSS's role with Tailwind?
PostCSS is a tool that transforms CSS using JavaScript, and Tailwind runs on top of it. You configure it via .postcssrc so your bundler understands Tailwind —but you don't need to learn PostCSS itself to use Tailwind.
Q. What does the content array in tailwind.config do?
It lists the files Tailwind should scan for class names. Tailwind only generates CSS for classes it finds in those files, so the paths must cover wherever you write classes —otherwise those styles get purged.
Q. Why is Tailwind's output so lightweight?
The build includes only the classes you actually use, not the whole library. If you never use shadow , that CSS never ships; if you use m-4 a hundred times, it's included once. That keeps the CSS bundle small.
Q. How do you apply a value that isn't in Tailwind's scale?
Use arbitrary-value syntax with square brackets, like w-[200px] . Prefer the built-in scale for consistency, and reach for brackets only when you genuinely need an exact, off-scale value.
Q. What are the tradeoffs of using Tailwind?
Pros: you style without switching files, iteration is fast, and the CSS bundle is tiny because unused classes are purged. Cons: there's an initial learning curve for the class names, and heavily-styled elements end up with long className strings that can hurt readability.
Q. How do you handle hover or responsive styles in Tailwind?
Prefix the class with a state or breakpoint —e.g. hover:bg-gray-400 applies on hover, and breakpoint prefixes apply at certain widths. The same utility just runs conditionally.
10.10

One-line recall

many ways to style React plain CSS / SASS styled-components (CSS-in-JS) MUI / Bootstrap / Chakra / Ant Tailwind = utility-first style without leaving JSX install tailwindcss + postcss npx tailwindcss init @tailwind base/components/utilities content array = scan paths m/p/px/py + size (rem) flex justify-between items-center bg-/text- + shade 50 →950 shadow-lg rounded-lg font-bold w-[200px] arbitrary value hover: prefix IntelliSense extension purged = lightweight bundle con: long className strings
Session 11

Data is the New Oil

Managing the data layer —enhancing components, lifting state, and sharing data across the tree without drilling props.

Every React app is two layers: a UI layer (your JSX) powered by a data layer (state, props, variables). Senior-level React is mostly about the data layer —who owns state, how it flows, and how to share it without threading props through every level.

11.1

The UI layer &the data layer

A useful mental model: the UI layer is the static JSX you render, and the data layer (state, props, local variables) is what drives it. The UI is powered by the data —get the data layer right and the UI follows.

  • UI is "dumb": JSX just displays what it's given. All the logic —what to show, when it changes —lives in the data layer.
  • Why it matters: managing data well is what separates a junior build from a senior one. Most of this guide is about handling that data layer cleanly.
11.2

Higher Order Components (HOCs)

A higher order component is a function that takes a component and returns a new, enhanced component . It doesn't change the original —it wraps it to add features (a label, extra props, behavior).

                        // contract: in a component, out an enhanced component
                        const
                        withPromotedLabel = (RestaurantCard) => {
  return
                        (props) => (
    <div ><label className="absolute"
                        >Promoted </label ><RestaurantCard {...props} />// pass props through
                        </div >);
};

// build the enhanced version, use it conditionally
                        const
                        RestaurantCardPromoted = withPromotedLabel
                        (RestaurantCard);
{resData.info.promoted
  ? <RestaurantCardPromoted resData={resData} />: <RestaurantCard resData={resData} />}
                    
  • It's just a function: takes a component in, returns a component out. The returned thing is itself a functional component returning JSX.
  • Keep it pure: an HOC doesn't modify the input component's code —it wraps and enhances it. Spread the props ({...props} ) down so the wrapped component still receives everything it needs.
  • Use it for: reusable enhancements —adding a label, injecting props, layering behavior —without duplicating the base component.
11.3

Controlled vs uncontrolled components

A component is uncontrolled when it manages its own state, and controlled when its parent drives it through props. There's no strict definition —it's about where the source of truth lives.

Uncontrolled

The component holds its own state (e.g. its own showItems ) and decides for itself. The parent can't coordinate it.

vs
Controlled

The parent passes the value in as a prop (e.g. showItems={...} ) and the component just follows. The parent is the source of truth.

  • Why make it controlled: when sibling components need to coordinate —like an accordion where opening one closes the others —a single owner (the parent) must decide who's open.
  • It's a spectrum: a controlled component can still have minor local state; what matters is that the main state driving it comes from the parent.
11.4

Lifting the state up

To let a parent coordinate its children, move the state out of the children and up into the parent. The parent holds one piece of state and passes both the value and a setter down.

                        // parent (RestaurantMenu) owns which one is open
                        const
                        [showIndex, setShowIndex] = useState
                        (null
                        );

{categories.map
                        ((cat, index) => (
  <RestaurantCategory
    data={cat}
    showItems={index === showIndex}        // controlled
                        setShowIndex={() => setShowIndex
                        (index)} // callback down
                        />))}
                    
  • The child no longer owns the state: it reads showItems from props and calls the setShowIndex callback on click —so the parent's single showIndex decides which accordion is open.
  • How a child updates the parent: you can't reach into a parent's state directly. Instead the parent passes a function down; the child calls it, and the parent updates its own state. Data flows down, events flow up.
  • Result: only one accordion open at a time —coordination that's impossible when each child manages itself.
Analogy ·recall

Think of a row of light switches wired to one central dimmer . If each switch controlled only its own bulb (state in the child), no switch could turn the others off. Lifting state up moves the wiring to a single panel (the parent): press any switch and it tells the panel, which lights that bulb and dims the rest.

11.5

React DevTools

The React DevTools browser extension adds two tabs to your console —Components and Profiler —that make the data layer visible.

  • Components tab: shows the component tree (a view of the virtual DOM) on the left and the selected component's props and state on the right —you can watch each accordion's showItems flip live.
  • Profiler tab: records interactions and shows which components re-rendered and how long each took —how you find slow components in a large app.
  • Note: extra nodes like RouterProvider in the tree come from libraries (React Router), not your code —don't panic when you see them.
11.6

Prop drilling &one-way data flow

React data flows one way: parent →child →grandchild. To get data to a deeply nested component, you pass it through every component in between —even ones that don't use it. That's prop drilling .

RestaurantMenu

has the data

RestaurantCategory

doesn't need it —just passes it

ItemList

actually wants it

  • The problem: intermediate components become unwilling couriers, threading props they don't use. One or two levels is fine; ten levels deep is a mess.
  • Don't abandon props: state and props are the heart of React —this isn't a reason to stop using them. It's a reason to avoid drilling the same data through many layers.
11.7

Context: a shared place for data

Context is React's built-in fix for prop drilling: a shared space any component can read from, no matter how deeply nested —ideal for data needed in many places, like the logged-in user or the theme.

                        // utils/UserContext.js
                        import
                        { createContext } from
                        "react"
                        ;
const
                        UserContext = createContext
                        ({ loggedInUser: "Default User"
                        });
export default
                        UserContext;

// read it in a functional component with a hook
                        const
                        { loggedInUser } = useContext
                        (UserContext);

// read it in a class component with Consumer
                        <UserContext.Consumer >{(data) => <h1 >{data.loggedInUser}</h1 >}
</UserContext.Consumer >
                    
  • createContext makes the context with a default value. Two ways to consume it: the useContext hook (functional components) or Context.Consumer with a callback (class components, which can't use hooks).
  • What belongs in context: only data genuinely shared across many components —logged-in user, theme, etc. Don't dump everything into context; props are still right for local, parent-to-child data.
  • It crosses lazy boundaries: a lazily-loaded page (e.g. About) still reads the current context value when it finally loads —the shared space persists.
11.8

Context.Provider: overriding &updating the value

To replace the default value, wrap part of your tree in Context.Provider and pass a value . Bind that value to state, and the whole subtree updates live when the state changes.

                        const
                        [userName, setUserName] = useState
                        ();

// tie context to state - everything inside re-reads on change
                        <UserContext.Provider value={{ loggedInUser: userName, setUserName }}><AppLayout /></UserContext.Provider >
                    
  • Provider sets the value: everything wrapped inside reads that value instead of the default. Wrap the whole app for global data, or just one branch (e.g. only the header) to scope it.
  • Bind to state for live updates: put the state value and its setter into value . Now any component can read the user and call setUserName —type in an input anywhere and every consumer updates instantly.
  • Providers nest: an inner provider overrides an outer one for its subtree. Header inside its own provider can show "Elon Musk" while the rest of the app shows "Akshay Saini" —the closest provider wins.
11.9

Context vs Redux (a preview)

Both give you a central store readable from anywhere. The difference is what they are and when you reach for them.

Context
Built into React

No install. Great for small and mid-size apps —and can scale to large ones with multiple contexts (user, theme, cart…).

Redux
External library

Installed separately. Adds structure for very large apps —middleware, thunks, devtools —at the cost of more setup.

  • Don't reach for Redux too early: a small or medium app rarely needs it —context handles shared data well. Redux earns its keep when the app gets big and you want its extra tooling and conventions.
11.10

Gotchas &nice-to-knows

Gotcha
HOC mutating the input

An HOC should wrap, not modify, the component passed in. Keep it pure and spread props through.

Gotcha
Child can't set parent state directly

Pass a setter function down as a prop; the child calls it. Data down, events up.

Gotcha
Drilling props many levels

Threading the same prop through 5 –10 layers is a smell —use context for widely-shared data.

Gotcha
Everything in context

Context isn't a dumping ground. Local parent-to-child data should stay as props.

Nice to know
Provider scope is flexible

Wrap the whole app or just a branch; nested providers override outer values for their subtree.

Nice to know
Bracket keys for odd names

JSON keys like @type aren't valid identifiers —read them with bracket notation: card["@type"] .

11.11

Interview Q &A

Q. What is a higher order component?
A function that takes a component and returns a new, enhanced component. It wraps the original to add features —a label, extra props, behavior —without modifying it. It's just a normal function that returns a component.
Q. What's the difference between a controlled and an uncontrolled component?
An uncontrolled component manages its own state internally. A controlled component is driven by its parent through props —the parent is the source of truth. You make a component controlled when a parent needs to coordinate it with its siblings.
Q. What does "lifting state up" mean?
Moving state out of child components and into their common parent, so the parent can coordinate them. The parent passes the value down as a prop and a setter function down too —children call the setter to update the parent's state. Data flows down, events flow up.
Q. How does a child component update its parent's state?
It can't do it directly. The parent passes a function (like a state setter) down as a prop; the child calls that function, and the parent updates its own state. This keeps React's one-way data flow intact.
Q. What is prop drilling and why is it a problem?
Passing data through many intermediate components just to reach a deeply nested one —even components that don't use the data have to forward it. It's fine for a level or two, but threading the same prop through many layers makes the code hard to maintain.
Q. What is the Context API and when do you use it?
Context is a shared space any component can read from, regardless of nesting —React's built-in fix for prop drilling. Use it for data needed in many places, like the logged-in user or theme. You create it with createContext and read it with useContext (or Context.Consumer in class components).
Q. How do you provide and update a context value?
Wrap a part of the tree in Context.Provider and pass a value ; everything inside reads that instead of the default. Bind the value to state and include the state setter in it —then any consumer can update the context, and the whole subtree re-renders live.
Q. Context vs Redux —what's the difference?
Both provide a central store accessible anywhere. Context is built into React, needs no install, and is great for small to mid-size apps (and can scale with multiple contexts). Redux is an external library that adds more structure —middleware, thunks, devtools —worth it for very large apps but overkill for small ones.
11.12

One-line recall

UI layer powered by data layer HOC = component in, enhanced out HOC is pure, spread props controlled vs uncontrolled lift state up to common parent data down, events up pass setter fn to child one-way data flow prop drilling = pass-through props React DevTools (Components + Profiler) createContext(defaultValue) useContext() hook (functional) Context.Consumer (class) Context.Provider value= bind context to state = live nested providers override context for shared data only context vs Redux
Session 12

Let's Build Our Store

Redux Toolkit —a predictable central store for large apps, and the one-way cycle that keeps it that way.

Redux is a separate library —not part of React, and not mandatory. For large apps it gives you one central store. The whole flow is one cycle: dispatch an action →a reducer updates a slice →subscribed components re-render .

12.1

When (and whether) to use Redux

Redux is a standalone state-management library, popularly paired with React but not built into it —and not required. You built this whole app for 11 sessions without it.

  • Not mandatory: small and mid-size apps don't need it —props and context handle them fine. Reach for Redux when the app is large, data is heavily shared, and many components read/write the same state.
  • It's separate from React: a common interview confusion is thinking Redux is React. It isn't —you install it. Alternatives like Zustand exist too.
  • Why it helps at scale: a predictable central store plus excellent debugging tools (Redux DevTools) make big apps easier to reason about.
12.2

Redux Toolkit (RTK), the modern way

Old "vanilla" Redux was complex, needed many extra packages, and demanded lots of boilerplate. Redux Toolkit (RTK) is the standard way to write Redux today —less code, less setup.

@reduxjs/toolkit
The Redux part

Builds the store, slices, reducers. The odd @ in the name is just branding —it's a normal package.

react-redux
The bridge

Connects Redux to React —gives you Provider , useSelector , useDispatch .

  • Two libraries do the job: install both —@reduxjs/toolkit for Redux logic and react-redux to wire it into React.
  • Why RTK exists: it directly addresses old Redux's three pain points —too complicated, too many packages, too much boilerplate.
12.3

The architecture: store, slices &the cycle

The store is one big object kept in a central place that any component can read or write. To stay organized, you split it into slices (logical partitions: a cart slice, a user slice, a theme slice…).

1 · dispatch

click fires an action

2 · reducer

action calls a reducer fn

3 · slice

reducer updates the slice

4 · selector

subscribed component re-renders

  • The one line to memorize: clicking Add dispatches an action , which calls a reducer function , which updates a slice of the store —and because the component is subscribed via a selector , it updates automatically.
  • You can't touch the slice directly: Redux forbids modifying a slice outside this path. Every change goes dispatch →reducer.
  • One big object is fine: keeping lots of data in the store is normal —slices just keep it tidy.
12.4

Building the store &wiring it in

Create the store with configureStore (from RTK), then make it available to React by wrapping the app in <Provider > (from react-redux).

                        // utils/appStore.js
                        import
                        { configureStore } from
                        "@reduxjs/toolkit"
                        ;
import
                        cartReducer from
                        "./cartSlice"
                        ;

const
                        appStore = configureStore
                        ({
  reducer: {                // the app's ONE big reducer
                        cart: cartReducer,      // each slice plugs in here
                        },
});

// App.js - provide the store to the whole app
                        <Provider store={appStore}><AppLayout /></Provider >
                    
  • Import from the right place: configureStore comes from RTK (a Redux job); Provider comes from react-redux (a React-bridge job). Knowing which is which is the whole point of two libraries.
  • Provider scope: wrap the whole app for global access, or just a branch if only part needs the store —same idea as a context provider.
12.5

Creating a slice

A slice bundles a name, its initial state, and its reducers. createSlice returns an object with auto-generated actions and a reducer —you export both.

                        import
                        { createSlice } from
                        "@reduxjs/toolkit"
                        ;

const
                        cartSlice = createSlice
                        ({
  name: "cart"
                        ,
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => {
      state.items.push
                        (action.payload);  // mutate directly
                        },
    removeItem: (state) => { state.items.pop
                        (); },
    clearCart: (state) => { state.items.length = 0
                        ; },
  },
});

export const
                        { addItem, removeItem, clearCart } = cartSlice.actions;
export default
                        cartSlice.reducer;
                    
  • Reducers map to actions: each function under reducers (e.g. addItem ) auto-generates a matching action you dispatch. A reducer receives (state, action) and changes state based on action.payload .
  • Export both: the named actions (to dispatch) and the default reducer (to plug into the store). This syntax is what RTK expects —it's not arbitrary.
  • reducer vs reducers : the store takes one big reducer (singular, a combination of slice reducers); a slice defines many reducers (plural). A classic mix-up —watch the s.
12.6

Writing: useDispatch &payload

To change the store, get the dispatch function from useDispatch and dispatch an action with your data. Whatever you pass becomes action.payload in the reducer.

                        import
                        { useDispatch } from
                        "react-redux"
                        ;
import
                        { addItem } from
                        "../utils/cartSlice"
                        ;

const
                        dispatch = useDispatch
                        ();

const
                        handleAddItem = (item) => {
  dispatch(addItem
                        (item));   // item becomes action.payload
                        };
                    
  • Payload plumbing: Redux wraps your argument into an action object { type, payload } and hands it to the reducer as the second arg —so action.payload is whatever you dispatched.
  • Pass a callback, don't call it: use onClick={() => handleAddItem(item)} , not onClick={handleAddItem(item)} —the second calls it immediately on render. (Plain JS, but a common bug.)
12.7

Reading: useSelector &subscribing

To read from the store, use the useSelector hook and select the exact slice portion you need. Doing so subscribes the component —it re-renders automatically when that data changes.

                        import
                        { useSelector } from
                        "react-redux"
                        ;

// GOOD - subscribe to the smallest slice you need
                        const
                        cartItems = useSelector
                        ((store) => store.cart.items);

// BAD - subscribing to the whole store
                        const
                        store = useSelector
                        ((store) => store);
const
                        cartItems = store.cart.items;
                    
  • Select the smallest portion: it's called a selector because you select a slice of the store. Subscribe to store.cart.items , not the whole store .
  • Why it's a performance trap: subscribing to the whole store re-renders this component on any change anywhere —even unrelated slices (a user login affecting a cart component). Narrow selectors avoid that.
  • Auto-update: once subscribed, dispatching elsewhere updates the slice and this component re-renders on its own —no manual wiring.
12.8

Mutating state &Immer

The biggest shift from old Redux: in RTK reducers you mutate state directly (state.items.push(...) ). Old Redux forbade this and forced you to return a new copy.

                        // OLD vanilla Redux - never mutate, return a new state
                        addItem: (state, action) => {
  const
                        newState = { ...state };
  newState.items.push
                        (action.payload);
  return
                        newState;
}

// RTK - just mutate; no return needed
                        addItem: (state, action) => { state.items.push
                        (action.payload); }
                    
  • Immer does the work: RTK uses the Immer library behind the scenes —it watches your "mutations," diffs old vs new, and produces a correct immutable copy. You write simple code; immutability still happens.
  • Two valid options: either mutate the existing state, or return a brand-new state. RTK accepts both —but don't half-do it.
  • Why state = [] fails: reassigning the state parameter just repoints a local variable —it doesn't mutate the real state Immer is tracking. Use state.items.length = 0 (mutate) or return a new object instead.
Analogy ·recall

Immer is like editing a tracked Google Doc . You just type your changes into the working draft (mutate freely), and the system quietly records a clean version history —you never manually photocopy the document first. Reassigning state = [] is like writing your edits on a sticky note instead of the doc: the note (local variable) changes, the real document doesn't.

12.9

Redux DevTools

A browser extension that activates on any Redux app —a major reason to use Redux at scale, since it makes a busy store debuggable.

  • Action log &diffs: every dispatched action appears with the new state and a git-style diff of exactly what changed.
  • Time travel: jump to any past state, replay the user's actions, or skip an action —you can reproduce the exact moment a bug appeared.
  • Trace &tests: it shows where each action was dispatched and even sketches a test case for the reducer.
12.10

Gotchas &nice-to-knows

Gotcha
Selecting the whole store

useSelector(s => s) re-renders on every change anywhere. Select the smallest slice you need.

Gotcha
reducer vs reducers

Store config uses reducer (one big); a slice uses reducers (many). Easy typo, real bug.

Gotcha
Reassigning state

state = [] won't clear the cart —it repoints a local var. Mutate (state.items.length = 0 ) or return a new state.

Gotcha
Calling vs passing the handler

onClick={fn(item)} runs on render. Use onClick={() => fn(item)} .

Nice to know
console.log a reducer's state

It logs an Immer proxy. Wrap it: console.log(current(state)) (current from RTK).

Nice to know
RTK Query for fetching

RTK includes RTK Query for data fetching —the modern replacement for old middleware/thunks.

12.11

Interview Q &A

Q. Is Redux part of React, and is it mandatory?
No on both. Redux is a separate state-management library that works with React (and other frameworks), and it's not required —small and mid-size apps do fine with props and context. Use it for large apps with lots of shared state.
Q. What is Redux Toolkit and why use it over vanilla Redux?
RTK is the standard modern way to write Redux. It fixes vanilla Redux's three big problems —it was too complicated, needed too many packages, and required too much boilerplate. RTK gives you concise APIs like configureStore and createSlice out of the box.
Q. Walk through the Redux data flow.
A UI event dispatches an action, which calls a reducer function, which updates a slice of the store. Any component subscribed to that slice via a selector then re-renders automatically. Reads use useSelector ; writes use useDispatch .
Q. What's a slice?
A logical partition of the store —like a cart slice, user slice, or theme slice. You create one with createSlice (giving it a name, initial state, and reducers), and plug its reducer into the store's reducer object.
Q. How do you read and write the store from a component?
Read with the useSelector hook, selecting the specific slice you need (which subscribes the component). Write with useDispatch to get dispatch , then dispatch an action —whatever you pass becomes action.payload in the reducer. Both hooks come from react-redux.
Q. Why select the smallest portion of the store?
A selector subscribes the component to whatever it returns. If you select the whole store, the component re-renders on any change anywhere —even unrelated slices. Selecting just the slice you need (e.g. store.cart.items ) keeps it efficient.
Q. Can you mutate state in a reducer? What about old Redux?
In RTK, yes —you mutate state directly (state.items.push(...) ), and RTK uses Immer behind the scenes to produce a correct immutable copy. Vanilla Redux was the opposite: never mutate, always return a new state. You can also return a new state in RTK if you prefer.
Q. What's the difference between context and Redux?
Both give a central store accessible anywhere. Context is built into React and great for small to mid apps. Redux is an external library with more structure and tooling (DevTools, RTK Query) —worth it for large apps but overkill for small ones.
12.12

One-line recall

Redux = separate, not mandatory RTK = modern standard @reduxjs/toolkit + react-redux store = one big object slices = logical partitions configureStore({ reducer }) <Provider store=> createSlice(name/initialState/reducers) dispatch →reducer →slice useDispatch + action.payload useSelector to subscribe select smallest portion mutate state directly (Immer) mutate OR return new state state = [] won't work reducer vs reducers current(state) to log Redux DevTools time-travel RTK Query for fetching
Session 13

Time for the Test

Testing a React app —the kinds of tests, the toolchain, and the render →query →assert rhythm every test follows.

Tests are how you stop new code from quietly breaking old code. Every React test is the same three beats: render a component →query the rendered output →assert something about it . The tools change; that rhythm doesn't.

13.1

Why test, and the three kinds

Any line of code can introduce a bug —including a bug in a feature you weren't even touching. Automated tests catch those regressions before users do, which is why testing is part of writing the code, not a chore bolted on after.

Unit
One piece, in isolation

Test a single component (or pure function) on its own —does it render, does it return the right value? The bulk of what you write.

Integration
Pieces working together

Test a flow that spans multiple components —type in a box, click search, see the list change. Several components must cooperate.

End-to-end
The whole journey

Drive the real app like a user, start to finish. Done with tools like Cypress, Puppeteer, or Selenium —usually owned by QA, not the dev.

  • Where developers live: you mostly write unit and integration tests. E2E is a separate discipline with its own tooling.
  • The payoff is regression safety: a passing suite means a change in one corner didn't silently break another. That confidence is the whole point.
13.2

The toolchain: RTL, Jest, jsdom, Babel

React testing leans on a small stack. Each piece has one job; templates like Create React App wire it up for you, but doing it by hand once shows what each part is actually for.

React Testing Library
The query/assert API

Built on top of DOM Testing Library, tuned for React. Gives you render , screen , fireEvent . It's a library, not a runner.

Jest
The test runner

Runs the tests and provides test/it , describe , and expect . RTL works with it. Runs your code through Babel.

jsdom
A fake browser

A browser-like environment in Node so tests can "render" DOM with no real browser. For Jest 28+, install jest-environment-jsdom separately.

Babel + presets
Translates your code

Babel transpiles modern JS/JSX so Jest can read it. @babel/preset-react (runtime automatic ) turns JSX into elements.

                        // scaffolding the setup (Parcel project, by hand)
                        npm i -D @testing-library/react jest babel-jest @babel/core
npx jest --init        // no TS, env: jsdom, coverage: yes, clearMocks: yes
                        npm i -D jest-environment-jsdom   // Jest 28+ needs this separately
                        // package.json
                        "scripts"
                        : {
  "test"
                        : "jest"
                        ,
  "watch-test"
                        : "jest --watch"
                        // re-runs on save, like HMR
                        }
                    
  • Two JSX errors you'll hit: "JSX isn't enabled" →add @babel/preset-react with { runtime: "automatic" } . "toBeInTheDocument is not a function " →import @testing-library/jest-dom , which adds the DOM matchers.
  • Parcel vs Babel clash: Parcel runs its own transpilation; add a .parcelrc (or scope the Babel config) so the two don't fight over your files.
  • Coverage folder: Jest writes a /coverage directory —add it to .gitignore so it doesn't pollute your history.
13.3

The first test: a pure function

Before any React, the shape of a test is clearest on a plain function. A test is test("description", callback) ; inside, you expect(value).toBe(expected) . That's the entire skeleton.

                        // sum.js
                        export const
                        sum = (a, b) => a + b;

// sum.test.js
                        import
                        { sum } from
                        "./sum"
                        ;

test
                        ("adds two numbers"
                        , () => {
  const
                        result = sum
                        (3, 4);
  expect
                        (result).toBe
                        (7);   // assertion
                        });
                    
  • expect is the assertion: it wraps a value, then a matcher (.toBe , .toEqual , .toBeTruthy …) states what should be true. .toBe uses Object.is under the hood.
  • File conventions: Jest auto-finds files in a __tests__ folder (the double underscore is "dunder") or any file ending .test.js / .spec.js (and the .ts variants).
13.4

Unit testing a component: render, query, assert

To test a UI component you first render it into jsdom, then reach into what was rendered through the screen object, then assert. screen exposes the queries; the most useful is getByRole .

                        import
                        { render, screen } from
                        "@testing-library/react"
                        ;
import
                        "@testing-library/jest-dom"
                        ;   // gives toBeInTheDocument
                        import
                        Contact from
                        "../Contact"
                        ;

test
                        ("should load Contact component"
                        , () => {
  render
                        (<Contact />);                          // 1. render to jsdom
                        const
                        heading = screen.getByRole
                        ("heading"
                        );   // 2. query
                        expect
                        (heading).toBeInTheDocument
                        ();          // 3. assert
                        });
                    
Prefer
getByRole

Find elements by their accessibility role —heading , button , textbox (the role for inputs). The recommended, robust query.

Fallbacks
getByText, getByPlaceholderText, getByTestId

By visible text, by placeholder, by an explicit data-testid . getByTestId always works when nothing else fits —it's the escape hatch.

  • Roles aren't tag names: an <input > 's role is textbox , not input . A wrong role won't crash silently —Jest lists the roles it actually found, which is how you discover the right one.
  • Narrow an ambiguous query: pass an options object —getByRole("button", { name: "Login" }) —when several elements share a role.
  • What a query returns: not a raw DOM node but a React element / fiber node —JSX is ultimately an object. Logging one shows an HTMLInputElement -like object with its props.
  • Failures are generous: when an assertion fails, Jest prints the entire rendered HTML, so you can see exactly what was there versus what you queried for.
13.5

getBy vs getAllBy, and matchers

Use getBy* when you expect exactly one match —more than one is an error . Use getAllBy* when you expect several; it returns an array you can count.

                        // expecting many - returns an array
                        const
                        inputs = screen.getAllByRole
                        ("textbox"
                        );
expect
                        (inputs.length).toBe
                        (2);
expect
                        (inputs.length).not
                        .toBe
                        (3);   // .not inverts any matcher
                        // text queries accept a regex - looser matching
                        screen.getByText
                        (/cart/i);   // matches "Cart - 0 items"
                    
  • Singular query, plural match = crash: getByRole("textbox") with two text boxes throws. Switch to getAllByRole —the error message tells you to.
  • Matchers are rich: toBe , toBeTruthy/Falsy , toBeGreaterThan , toBeNull , toBeNaN , and the .not inverter. Type a . after expect(x) and the editor lists them.
  • Regex over exact strings: getByText("Cart - 0 items") is brittle; getByText(/cart/i) survives small copy changes.
13.6

Components that need context: providers in tests

jsdom understands JSX, but it doesn't know about Redux or React Router. Render a component that uses useSelector or <Link > on its own and it crashes —you must wrap it in the same providers the real app gives it.

                        import
                        { render, screen, fireEvent } from
                        "@testing-library/react"
                        ;
import
                        { Provider } from
                        "react-redux"
                        ;
import
                        { BrowserRouter } from
                        "react-router-dom"
                        ;
import
                        appStore from
                        "../utils/appStore"
                        ;
import
                        Header from
                        "../Header"
                        ;

test
                        ("Header has a login button"
                        , () => {
  render
                        (
    <BrowserRouter ><Provider store={appStore}><Header /></Provider ></BrowserRouter >);
  const
                        btn = screen.getByRole
                        ("button"
                        , { name: "Login"
                        });
  expect
                        (btn).toBeInTheDocument
                        ();
});
                    
  • Read the failure to find the missing provider: "could not find react-redux context" →wrap in <Provider > . "error in Link component" →wrap in <BrowserRouter > . Each missing context fails with its own clear message.
  • Same store as the app: import the real appStore so selectors resolve against actual initial state.
Analogy ·recall

Testing a component is like auditioning an actor on a bare stage . jsdom is the empty stage —it has the floor and the lights (JSX, the DOM), but not the props the actor reaches for. If the role calls for them to pick up a phone (useSelector ) or walk through a door (<Link > ), you have to set those props on stage first (<Provider > , <BrowserRouter > ) —otherwise they grab at thin air and the scene falls apart.

13.7

Firing events: clicks and typing

To simulate a user, import fireEvent from RTL. fireEvent.click triggers a click; fireEvent.change simulates typing —and you hand it the event object the browser would normally build.

                        // click toggles Login -> Logout
                        fireEvent
                        .click
                        (loginButton);
expect
                        (screen.getByRole
                        ("button"
                        , { name: "Logout"
                        }))
  .toBeInTheDocument
                        ();

// typing = a change event with a faked e.target.value
                        fireEvent
                        .change
                        (searchInput, {
  target: { value: "burger"
                        },
});
                    
  • You fake the event object: in a real browser e.target.value comes from the DOM; in jsdom you supply { target: { value: "burger" } } yourself to mimic what an onChange handler receives.
  • Assert the consequence, not the click: after firing, query for the new state (the "Logout" button, the filtered list) —that's what proves the behavior works.
13.8

Props &mock data

A component that takes props (like a restaurant card) needs data supplied in the test. You don't hit the network —you create mock data : a JSON snapshot of a real response, kept in a /mocks folder and imported.

                        import
                        MOCK_DATA from
                        "../mocks/resCardMock.json"
                        ;

test
                        ("renders card from props"
                        , () => {
  render
                        (<RestaurantCard resData={MOCK_DATA} />);
  const
                        name = screen.getByText
                        ("Leon's Burgers &Wings"
                        );
  expect
                        (name).toBeInTheDocument
                        ();
});
                    
  • Where mock data comes from: log the real prop while the app runs, copy the object, and paste it into a JSON file. Now the test is deterministic and offline.
  • Pass it as the prop: the mock fills the same prop the parent normally passes (resData ), so the component renders exactly as it would live.
13.9

Integration testing: mocking fetch &a full flow

An integration test renders a whole feature —e.g. the Body with its search box, cards, and API call. Because jsdom has no fetch (it's a browser superpower, not core JS), you replace global.fetch with a mock that resolves your mock data in the same promise shape.

                        import
                        MOCK_RESLIST from
                        "../mocks/resListMock.json"
                        ;
import
                        { act } from
                        "react-dom/test-utils"
                        ;

// fetch returns a promise -> json() returns a promise -> data
                        global
                        .fetch = jest.fn
                        (() => {
  return
                        Promise.resolve
                        ({
    json: () => Promise.resolve
                        (MOCK_RESLIST),
  });
});

test
                        ("search filters the list to 4 cards"
                        , async
                        () => {
  await
                        act
                        (async
                        () =>
    render
                        (<BrowserRouter ><Body /></BrowserRouter >)
  );

  const
                        input = screen.getByTestId
                        ("searchInput"
                        );
  fireEvent
                        .change
                        (input, { target: { value: "burger"
                        } });
  fireEvent
                        .click
                        (screen.getByRole
                        ("button"
                        , { name: "Search"
                        }));

  const
                        cards = screen.getAllByTestId
                        ("resCard"
                        );
  expect
                        (cards.length).toBe
                        (4);
});
                    
  • Match fetch's exact shape: real fetch resolves to a response whose .json() also returns a promise. Your mock must nest Promise.resolve({ json: () =>Promise.resolve(data) }) or the component's await res.json() breaks.
  • Wrap async renders in act : when rendering triggers state updates (a fetch then setState ), Jest warns unless you await act(async () =>render(...)) . act comes from react-dom/test-utils .
  • data-testid for counting: tag each card with data-testid="resCard" in the component, then getAllByTestId counts them —assert 20 before search, 4 after. Snapshotting before-and-after proves the filter actually ran.
  • Tests run offline: because fetch is faked, the suite needs no network and no browser —it works with Wi-Fi off.
13.10

Organizing tests &lifecycle hooks

Group
describe blocks

Wrap related tests in describe("name", () =>{...}) to group them. Purely organizational —can be nested, changes nothing about how tests run.

Alias
it === test

it is just an alias of test . Many prefer it("should ...") because it reads as a sentence. Pick one and stay consistent.

Setup
beforeAll / beforeEach

beforeAll runs once before the whole file; beforeEach runs before every test. Use for shared setup.

Teardown
afterEach / afterAll

afterEach runs after each test, afterAll once at the end. Use for cleanup. Order: beforeAll →(beforeEach →test →afterEach)…→afterAll.

  • Hover for docs: in the editor, hovering a matcher like toBeTruthy shows what it does (e.g. it fails on JavaScript's six falsy values). Cheap way to learn the API as you write.
13.11

Interview Q &A

Q. What are the three types of testing?
Unit testing checks one component or function in isolation. Integration testing checks several components working together in a flow. End-to-end testing drives the whole app like a real user. Developers mostly write unit and integration tests; E2E uses tools like Cypress or Selenium.
Q. What's the difference between Jest and React Testing Library?
Jest is the test runner —it executes tests and gives you test , describe , and expect . React Testing Library is a library built on DOM Testing Library that renders components and queries the output (render , screen , fireEvent ). They're used together.
Q. What three steps does a React test follow?
Render the component into jsdom, query the rendered output through screen , then assert something with expect . Render, query, assert —every test repeats that rhythm.
Q. What is jsdom and why do tests need it?
jsdom is a browser-like environment running in Node. Tests have no real browser, so jsdom provides a fake DOM to render into. It understands JSX and JavaScript but not browser superpowers like fetch , which is why those have to be mocked.
Q. Why prefer getByRole, and what's getByTestId for?
getByRole finds elements by their accessibility role and is the most robust query. getByText is a fallback. getByTestId is the escape hatch —you add a data-testid attribute and query it when nothing else works, useful for counting elements like cards.
Q. When do you use getAllBy instead of getBy?
Use getBy* when exactly one element should match —multiple matches throw an error. Use getAllBy* when several match; it returns an array, so you can assert on its length (e.g. two input boxes, four cards).
Q. A component uses Redux and Link —why does the test crash, and how do you fix it?
jsdom doesn't know about Redux or React Router, so useSelector and <Link > fail with "no context" errors. Wrap the rendered component in the same providers the app uses —<Provider store={appStore }> for Redux and <BrowserRouter > for routing.
Q. Why mock fetch, and what shape must the mock have?
fetch is a browser feature, not part of core JavaScript, so jsdom doesn't have it —and tests shouldn't make real network calls anyway. Replace global.fetch with a Jest mock that returns a promise resolving to an object whose json() also returns a promise resolving to your mock data, matching real fetch exactly.
Q. How do you simulate a user typing and clicking?
Use fireEvent from RTL. fireEvent.click(element) simulates a click. fireEvent.change(input, { target: { value: "burger" } }) simulates typing —you supply the event object's target.value that the browser would normally provide.
Q. What's the difference between test and it, and describe?
it is just an alias for test —identical behavior, often chosen because it("should ...") reads as a sentence. describe groups related tests into a block for organization; it can be nested and doesn't change how tests run.
render →query →assert unit / integration / E2E Jest = runner, RTL = queries jsdom = fake browser @babel/preset-react (JSX) @testing-library/jest-dom matchers screen.getByRole preferred getBy = one, getAllBy = many getByTestId = escape hatch wrap in Provider + BrowserRouter fireEvent.click / .change fake e.target.value mock data in /mocks JSON mock global.fetch (nested promises) await act for async renders describe groups, it === test .gitignore /coverage