What you’ll learn
- ●What is React?
- ●What is JSX?
- Components & PropsFree account
- State & useStateFree account
- useEffect & lifecycleFree account
- Conditional renderingFree account
- Lists & keysFree account
- Forms & inputsFree account
- Context APIFree account
- Custom hooksFree account
- Redux basicsFree account
- Performance & memoFree account
- React RouterFree account
- Interview recapFree account
- ●Project 1: Counter
- Project 2: Signup form + validationFree account
- Project 3: Todo app (full build)Free account
What is React?
Imagine building a house with a Lego set. You do not build the whole house at once — you snap together small blocks. If one block breaks, you swap just that block, not the whole house.
React works exactly like this. It breaks a website into small "components" (blocks) — Navbar, Button, Card. Each is reusable, and if one changes, only that piece updates — the whole page does not reload.
🌍 Real-world example: On the Instagram feed, like one post and only that post's heart turns red; the whole feed does not refresh. That is React: only what changed is drawn again (a "re-render").
Standard definition: React is an open-source JavaScript library developed by Meta for building user interfaces using reusable, component-based architecture with a virtual DOM for efficient rendering.
function Welcome() {
return <h1>Hello, Hirenix!</h1>;
}
// Use it on screen:
<Welcome />What is JSX?
Think of JSX like writing a WhatsApp message with emojis mixed into text — the emojis aren't plain text, but your phone understands and shows them. JSX mixes HTML-looking tags right inside JavaScript so your UI reads naturally.
But the browser only understands plain JavaScript, not JSX. So a tool called Babel quietly translates your JSX into plain React.createElement(...) calls before the browser ever sees it.
💡 What is Babel? Babel is a free tool called a compiler — it converts code the browser can't read (like JSX, or the newest JavaScript features) into old, plain JavaScript that every browser understands. It runs automatically when your project builds, like a translator working in the background.
🌍 Real-world example: You write
<h1>Hello</h1>inside a JS file. Babel turns it intoReact.createElement('h1', null, 'Hello'), which produces a plain JS object React can render. You write easy HTML-like code; React gets the objects it needs.
Standard definition: JSX (JavaScript XML) is a syntax extension for JavaScript that lets you write HTML-like markup inside JavaScript; it is compiled (by Babel) into React.createElement calls that return React elements.
const name = "Hirenix";
const heading = <h1>Hello, {name}!</h1>;
// Babel turns the JSX into:
// React.createElement('h1', null, 'Hello, ', name, '!')Project 1: Counter
What we're building: A Counter app — a number on the screen with +, −, and Reset buttons. It's tiny, but it teaches the single most important loop in all of React: event → setState → React re-runs your component → re-render → UI updates. Master this and everything else is just variations of it.
React concepts it teaches: useState, event handlers (onClick), the setter function, functional updates (setCount(c => c + 1)) vs value updates (setCount(count + 1)), and the golden rule — you change STATE, not the DOM.
Step 0 — The mental model (read this first, 30 seconds)
In plain HTML/JavaScript, if you wanted to change a number on screen you'd grab the element and write to it: document.getElementById('x').innerText = 5. You touch the DOM by hand.
React works backwards. You never touch the DOM. Instead you keep a piece of data called state, and you tell React what the screen should look like for a given state. When state changes, React redraws the screen for you. Your only job is: when something happens, update the state. React does the drawing.
💡 State = a value React watches. When it changes, React re-draws the component that uses it.
Keep that picture in your head. Now let's build.
Step 1 — Just show a number
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default Counter;
What's happening: useState(0) creates one piece of state and starts it at 0. It hands you back an array with exactly two things, and we unpack them with const [count, setCount] = ...:
count— the current value (0right now). This is a read-only snapshot.setCount— the setter, the ONLY correct way to change that value.
Inside the JSX, {count} drops the current value into the page, so the screen shows Count: 0. Notice: we didn't touch the DOM, we just described what to show for the current state. That's the whole idea.
💡 Why not just
let count = 0? Because a normal variable, when you change it, doesn't tell React anything — the screen would never update.useStategives React a value it watches.
Step 2 — The + button (this is the big one)
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
export default Counter;
What's happening: This is the core loop — trace it slowly, because every button in every React app works exactly this way:
- You click the
+button. - That fires the button's
onClick, which runs the arrow function() => setCount(count + 1). countis0right now, so this callssetCount(0 + 1)→setCount(1).- The setter tells React: "hey, this state's new value is 1."
- React responds by re-running the whole
Counterfunction from the top. - On this new run,
useStategives back the updated value — nowcountis1. - The JSX produces
Count: 1, React compares it to what's on screen and re-renders just the changed part. - You see
Count: 1.
💡 onClick is React's way to attach a click handler. We give it a function (
() => ...), not a call. If you wroteonClick={setCount(count + 1)}(no arrow), it would run immediately during render, not on click — a classic beginner bug.
The button never touched the number on screen. It changed state. State changing is what caused React to redraw. Say it out loud: the button changes state, not the DOM.
Step 3 — The − button (decrement)
<p>Count: {count}</p>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>
What's happening: Exact same loop, opposite math. Click − → onClick runs setCount(count - 1). If count is 3, that's setCount(2). Setter tells React state changed → React re-runs Counter → count is now 2 → screen shows Count: 2. Two buttons, one shared state — both just call the setter with a different number.
Step 4 — The Reset button
<button onClick={() => setCount(0)}>Reset</button>
What's happening: Reset is the simplest of all — it doesn't care what count currently is. It calls setCount(0), hard-setting state back to 0. Setter → React re-runs Counter → count is 0 → screen shows Count: 0. Same loop again. Once you see the loop, you can add any button you want.
Step 5 — Functional update: setCount(c => c + 1)
Swap the + handler from a value update to a functional update:
<button onClick={() => setCount(c => c + 1)}>+</button>
What's happening: Both versions add 1, so why bother? The difference is where the +1 starts from.
setCount(count + 1)— a value update. It usescountas captured at the moment this click handler was created. That snapshot can be stale.setCount(c => c + 1)— a functional update. You pass a function. React calls it and hands it the latest value asc, and whatever you return becomes the new state. No stale snapshot.
Why it matters — imagine a "+3" button that stacks three updates in one click:
// BROKEN: all three read the same stale count (say 0) → 0+1 each → ends at 1, not 3
<button onClick={() => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }}>+3</button>
// CORRECT: each gets the latest value → 0→1→2→3
<button onClick={() => { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); }}>+3</button>
💡 Rule of thumb: if your new state depends on the old state, use the function form
setCount(c => c + 1). It's always safe.
Step 6 — A small twist: can't go below 0
Let's add a rule: the counter should never show a negative number. This shows conditional logic living inside the setter.
<button onClick={() => setCount(c => (c > 0 ? c - 1 : 0))}>-</button>
What's happening: On click, the functional update runs with the latest c. The ternary c > 0 ? c - 1 : 0 says: if we're above zero, go down by one; otherwise stay at 0. So at count = 0, clicking − returns 0 — React re-runs, count is still 0, screen doesn't budge. The floor is enforced by what value you put into state, not by disabling anything. (You could also disable the button — shown in the practice challenges.)
💡 Ternary
condition ? a : b= a one-line if/else. Reads as "if condition then a, else b."
🔎 The full flow (recap the wiring)
Here's the finished app, click by click:
- Page loads →
Counter()runs →useState(0)setscount = 0→ JSX showsCount: 0. - Click + →
onClickrunssetCount(c => c + 1)→ setter tells React "state changed" → React re-runsCounter→countis now1→ re-render → screen showsCount: 1. - Click − →
onClickrunssetCount(c => (c > 0 ? c - 1 : 0))→ new value computed from latestc→ setter → re-run → re-render → number goes down (never below 0). - Click Reset →
onClickrunssetCount(0)→ setter → re-run → re-render → showsCount: 0.
Every single button follows the same four beats: event → setState → React re-runs the component → UI updates. You never wrote a single line that touches the number on screen directly — you only ever changed state.
✅ What you just learned
useState— how to create a state value and its setter:const [count, setCount] = useState(0).- Event handlers —
onClick={() => ...}attaches a function that runs on click (pass a function, not a call). - The setter drives re-render — calling
setCount(...)is what tells React to re-run the component and update the UI. - You change STATE, not the DOM — the button changes state; state changing is what redraws the screen.
- Functional updates —
setCount(c => c + 1)uses the latest value and is the safe choice when new state depends on old state;setCount(count + 1)can use a stale snapshot. - Conditional logic in a setter — using a ternary to keep state within rules (never below 0).
- The universal loop — event → setState → re-run → re-render. Every interactive React app is built on this.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>Counter</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(c => (c > 0 ? c - 1 : 0))}>-</button>
<button onClick={() => setCount(c => c + 1)}>+</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
export default Counter;Reactinterview questions & answers
10 sample questions below — 174+ in the full bank inside.
How does the && operator work in JavaScript to enable conditional rendering?
&& evaluates the left side first; if it's falsy, it short-circuits and returns that falsy value without evaluating the right side. If it's truthy, it evaluates and returns the right side. In JSX, {cond && <Comp/>} renders <Comp/> only when cond is truthy, otherwise React skips rendering the falsy value.
In simple terms: It's like a security checkpoint — if the first check fails, you never even reach the second gate, and whatever failed the first check is what gets reported. Example: {isAdmin && <AdminPanel/>} — if isAdmin is false, JavaScript never even looks at <AdminPanel/>, it just returns false and React shows nothing.
When should you use the ternary operator vs the && operator for conditional rendering?
Use the ternary (cond ? A : B) when you need to render one of two things (an either/or). Use && (cond && A) when you either show something or show nothing — there's no 'else' branch.
In simple terms: Ternary is like choosing between two doors — you always walk through one of them. && is like a single door with a lock — if the condition is true, the door opens and you see the room; if false, you see nothing at all. Example: {loading ? <Spinner/> : <Content/>} (two outcomes) vs {hasNewMessages && <Badge/>} (show or nothing).
How do you do conditional rendering in React?
You render different UI based on a condition using a ternary (cond ? A : B), the && operator (cond && A), or an early return. React renders whatever the expression evaluates to.
In simple terms: It's like an if-check for what shows on screen. If the user is logged in, show "Logout"; otherwise show "Login". You write cond ? A : B or cond && A right inside JSX, and React shows whatever comes out. Example: {isLoggedIn ? <Logout/> : <Login/>}.
What happens if a component returns null?
React renders nothing for that component — no DOM node is created at all, but the component still exists in the React tree and can still re-render later if props/state change.
In simple terms: It's like a chef who gets an order but decides not to plate anything for it this round — the kitchen (React) still knows the order exists, it just serves an empty plate to the table right now. Example: function Banner({show}) { if (!show) return null; return <div>Sale!</div>; } — when show is false, nothing appears on screen.
What does 'one-way data flow' mean in React, in terms of props?
Data flows down: parent components pass data to children via props, and children can never directly change a parent's data. If a child needs to affect the parent's state, the parent passes down a callback function as a prop, and the child calls that function — the update still happens 'up' through the parent, not by the child reaching backward.
In simple terms: Think of it like a river — water (data) only flows downstream (parent to child), never upstream. If the child needs to send a message back up, it doesn't dam the river; it uses a phone (a callback prop) the parent gave it. Example: <Child onSave={handleSave} /> — Child calls props.onSave(data) to notify the parent, but never reaches into the parent's state directly.
What is the difference between a component and props?
A component is a reusable block of UI (like a Lego block). Props are the inputs you pass into that block to customise it; they flow one-way from parent to child and are read-only inside the child.
In simple terms: Think of a component like a rubber stamp — carve it once, stamp it many times. Props are the ink colour or date you set each time you stamp, so the same stamp gives slightly different output. Example: <Button label="Save" /> and <Button label="Delete" /> are the same Button with different props.
What is the children prop and when do you use it?
children is a special prop that holds whatever is nested between a component's opening and closing tags. It's used to build wrapper/layout components (like Card, Modal, Layout) that don't know in advance what content they'll display — they just render {props.children} wherever the content should appear.
In simple terms: Think of a picture frame (the wrapper component) — it doesn't care what photo you put inside it, it just displays whatever you slot in. Example: <Card><h2>Title</h2><p>Body</p></Card> — inside Card, props.children is that <h2> and <p>, and Card just renders <div className="card">{props.children}</div>.
Are props mutable inside a component? What happens if you try to change a prop directly?
No, props are read-only inside the receiving component. React expects components to be 'pure' with respect to props — a component should never modify its own props. If you need different data, ask the parent to pass new props (usually by lifting state up and re-rendering).
In simple terms: Props are like a rented car — you can drive it and use it, but you don't get to repaint it or swap the engine; if you want a different car, you go back to the rental company (the parent). Example: function Greeting(props) { props.name = 'Hacked'; ... } is an anti-pattern — mutating props.name directly doesn't reflect in the parent and breaks React's data flow assumptions.
How do you give a prop a default value in a modern function component?
Use a default parameter value in the function signature when destructuring props, e.g. function Button({ size = 'md', label }) { ... }. The old class-based defaultProps static property still works but default parameters are the current, preferred approach for function components.
In simple terms: It's like a food order form that says 'no size selected? we'll give you medium by default.' Example: function Avatar({ size = 40 }) { return <img width={size} /> } — calling <Avatar /> with no size prop uses 40, but <Avatar size={80} /> overrides it with 80.
How does optional chaining (?.) help with conditional rendering of data that might not exist yet?
Optional chaining lets you safely read nested properties that might be null/undefined without crashing — it returns undefined instead of throwing, which you can then combine with && or a fallback (?? / ternary) to conditionally render.
In simple terms: It's like knocking on a door and politely checking if anyone's home before walking in, instead of assuming the room exists and barging in (which crashes if it doesn't). Example: {user?.address?.city && <p>{user.address.city}</p>} won't crash even if user or address is still null while data is loading.
164+ more React questions inside
Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.
Unlock all questions — freeReady to practise React?
Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.