React is a JavaScript library for building user interfaces out of components. It is declarative: you describe what the UI should look like for the current data, and React works out which DOM changes bring the screen there.
Imperative way: with the plain DOM you find nodes and change them step by step (el.textContent = ..., appendChild), and every new state needs its own set of steps.
Declarative way: a component is a function of props and state that returns JSX; when the data changes, React calls it again and updates only what differs.
Components: the UI is split into small reusable pieces, each with its own markup and logic, composed like functions.
A library, not a framework: routing, data fetching and the build come from React Router, TanStack Query, Vite or a framework such as Next.js.
Renderers: the same model drives the web (react-dom) and mobile apps (React Native).
ReactJunior
React Fundamentals
JSX, components, props, children and composition, conditional rendering, lists and keys, one-way data flow, React 19 basics
Questions closed
0 / 21
- 0Review
- 13Asked often
Questions
The virtual DOM is the tree of plain JavaScript objects (React elements) that your components return. On each render React compares the new tree with the previous one and applies only the differences to the real DOM.
Why it exists: you write UI as if everything re-renders on each change, while the browser receives only the minimal set of DOM operations.
Not faster than careful manual DOM code: a hand-written update does less work; the point is to be fast enough while the code stays declarative.
The cost: React still runs your component functions and compares the trees, so a large tree that re-renders often can be slow; memo, the React Compiler and keeping state close to where it is used address that.
The comparison is heuristic: an element of a different type replaces its whole subtree, and key tells React which list items match.
Naming: the React docs now say "React elements" and "reconciliation"; Fiber is the internal structure that does this work.
JSX is a syntax extension that lets you write markup inside JavaScript. A compiler (Babel, SWC, esbuild or TypeScript) turns each tag into a function call that creates a React element, a plain object describing what to render.
The automatic runtime (React 17+): <h1 className="t">Hi</h1> compiles to jsx('h1', { className: 't', children: 'Hi' }) from react/jsx-runtime, so files no longer need import React.
Expressions in braces: {user.name}, {items.length > 0 && ...}; only expressions, not if or for statements.
Attributes are camelCase props: className, htmlFor, onClick, tabIndex; style takes an object.
One root: a component returns one element, or a Fragment <>...</> around several.
Every tag closes: <img />, <br />.
Escaped by default: a string in braces is rendered as text, so <script> inside it never runs.
A component is a JavaScript function that takes props and returns JSX. Its name must be capitalized because JSX treats a lowercase tag such as <button> as a built-in HTML element and a capitalized one such as <Button> as a variable to call.
What goes wrong: <profile /> compiles to jsx('profile', ...) with a string type, so React creates an unknown <profile> DOM element instead of calling your function.
Function components are the norm: class components with render() are legacy; you meet them in old code and in error boundaries.
Declare components at the top level of a module, never inside another component: that creates a new type on every render and resets its state.
Components nest: a component renders other components, and that tree is your UI.
A class component extends React.Component, keeps its state in this.state and reacts to mounting and updates in lifecycle methods; a function component is a plain function that uses hooks. Function components are the default, and only an error boundary still needs a class.
State: a class keeps one this.state object that this.setState({ a }) merges into; useState holds one value per call, and its setter replaces it.
**this**: class handlers need bind or class-field arrows; a function component has no this.
Latest props vs a snapshot: this.props always points to the latest props, so a timeout started before a re-render reads the new values; a function component's handler keeps the props of its own render.
Reusing logic: higher-order components and render props for classes, custom hooks for functions.
Error boundaries need static getDerivedStateFromError or componentDidCatch, which have no hook; react-error-boundary wraps such a class for you.
Classes still work, but the React Compiler optimizes only function components and hooks.
Props are the inputs a parent passes to a child component, like arguments to a function. They are read-only: the parent owns that data, and a component must render the same output for the same props, so changing them in the child would break the single source of truth.
Any value: strings, numbers, objects, arrays, functions (callbacks) and JSX.
Destructure with defaults: function Button({ size = 'md', children }).
To change what a child shows, the parent renders it again with new props; to let the child ask for a change, the parent passes a callback such as onChange.
Mutating props (props.items.push(x)) changes the parent's object behind React's back: nothing re-renders, and other components see the change unexpectedly.
Spread with care: {...rest} forwards unknown props to a DOM element, handy for wrappers but an easy way to leak invalid attributes.
Props are data a component receives from its parent and cannot change; state is data a component owns and changes itself through a setter, such as the one useState returns. A change in either makes React render the component again.
Who owns it: the parent owns props; the component owns its state.
Who changes it: props change only when the parent re-renders with new values; state changes through setX, usually in an event handler.
How they connect: one component's state often becomes another's props: the parent keeps selectedId in state and passes it down.
What belongs in state: only what changes over time and cannot be computed from props or other state; a value derived from props is computed during render.
Snapshot: both are fixed for one render; a new value shows up on the next render.
children is the JSX nested between a component's opening and closing tags, passed to it as a prop. Components reuse each other by wrapping and passing JSX (composition), not by extending classes, which keeps each piece independent and easy to swap.
Containers that do not know their content: Card, Modal and Layout render {children} in the right place.
Several slots: pass JSX in named props such as header and footer alongside children.
Specialization: a DangerButton renders <Button variant="danger" {...props} /> instead of subclassing it.
Less prop drilling: the parent builds the inner component itself and passes it in, so the layout in between never sees its props.
Type: children: ReactNode covers elements, strings, numbers, arrays, null and false.
Use plain JavaScript: an if with an early return before the JSX, the ternary cond ? <A /> : <B /> inside it, and cond && <A /> to render something or nothing. Returning null renders nothing.
**if with early return: best when a whole branch differs, such as the loading or error screen.
Ternary: two inline alternatives; nest at most one level, otherwise move the branch into a variable or a component.
&&**: only with a real boolean on the left; 0 && <X /> renders 0.
Variable: let content; if (...) content = <A />; and then {content} keeps complex JSX readable.
Hiding vs removing: a condition unmounts the component and resets its state; a CSS hidden attribute keeps it mounted.
You map an array to JSX: items.map(item => <Row key={item.id} ... />). The key tells React which item is which between renders, so when items are added, removed or reordered it moves the right DOM nodes and keeps each item's state with that item.
Unique among siblings and stable across renders: an id from the data is ideal.
**Never Math.random(): a new key on every render remounts every row and loses its state and focus.
The index as key is fine only for a static list that is never reordered, filtered or inserted into.
Without a key React warns ("Each child in a list should have a unique "key" prop") and falls back to the index.
key is not a prop**: the child cannot read props.key; pass the id separately if it needs it.
The key goes on the outermost element returned from map, including <Fragment key={...}>.
A Fragment, written <>...</> or <Fragment>, groups several elements without adding a DOM node. You need it when a component must return several siblings, because a component returns a single root.
Valid HTML: a component that returns <td> cells for a <tr>, or <dt> and <dd> pairs for a <dl>, cannot wrap them in a <div>.
Layout: an extra wrapper breaks flex and grid layouts and selectors such as ul > li.
Keys: the short syntax takes no attributes; inside map use <Fragment key={id}>.
An array works too, but every item then needs its own key and the code is noisier.
Use a framework such as Next.js or React Router when the app needs routing, data loading and server rendering, or Vite for a client-only app. create-react-app was deprecated in February 2025: it was unmaintained, slow, and had no answer for routing, data fetching or code splitting.
Vite: npm create vite@latest my-app -- --template react-ts; a dev server on native ES modules that starts instantly and an optimized production build.
Next.js: App Router, React Server Components, server rendering and file-based routing; npx create-next-app@latest.
React Router v7 in framework mode, the successor of Remix: npx create-react-router@latest.
Existing CRA apps keep working but get no updates; the usual migration target is Vite.
TypeScript by default: every template has a TS variant, and most teams start there.
With an empty cart it renders a stray 0 under the heading. items.length && ... evaluates to the number 0, and React renders numbers as text; it skips only false, true, null and undefined.
**How && works**: it returns the left operand itself when that operand is falsy, not false, so the whole expression is 0.
**Why the 0 shows**: React treats a number child as a text node, exactly like {count}.
Fix: make the left side a boolean, items.length > 0 && ..., or use a ternary with null for the empty branch.
In React Native the same code crashes with "Text strings must be rendered within a <Text> component", so the bug is louder there.
The list uses the array index as key. After the first item is removed, the second item gets index 0, so React treats it as the old first row and keeps that Row instance, including its note state.
What React sees: keys 0, 1, 2 became 0, 1; it passes new title props to rows 0 and 1 and removes row 2, the last one.
State lives with the component instance, which React matches by type, position and key, not by the data you meant.
Fix: a stable id from the data, key={item.id}; then the row with the deleted id is removed and the others keep their own state.
Index keys are safe only for lists that never reorder, filter or insert.
Data flows down: a parent passes values to its children as props, and the children cannot change them. To report something back, a child calls a callback the parent passed as a prop, and the parent updates its own state, which flows down again.
One source of truth: each piece of state has one owner, so you always know where a value comes from and who can change it.
Callbacks go down too: onSelect, onChange, onClose; the child says what happened, the parent decides what to do.
Siblings share data through their closest common parent, which holds the state (lifting state up).
Contrast with two-way binding (Angular [(ngModel)], Vue v-model): React makes the update explicit with value plus onChange.
Deep trees: when a prop passes through many layers, use composition or Context; the flow is still one-way.
React may call a component many times, in any order, or throw a render away (StrictMode, concurrent rendering), so rendering must only compute JSX from props, state and context. Changing anything that existed before the render is a side effect and belongs in an event handler or an effect.
Pure: the same inputs give the same JSX, and nothing outside the function changes.
Side effects in render: mutating props, state or a module variable, writing to localStorage, calling fetch, setting document.title, subscribing to events.
Local mutation is fine: creating an array inside the render and pushing to it leaks nothing.
StrictMode calls components twice in development to expose impure code: the doubled output shows the bug.
Why it pays off: React can skip, pause and restart rendering, and the React Compiler can memoize automatically, only because components are pure.
SearchBox is declared inside SearchPage, so every render creates a new function, which React sees as a new component type. It unmounts the old input and mounts a fresh one, and the fresh input has no focus.
Type identity: React compares element types with ===; a function created during render never equals the previous one.
Remount, not update: the DOM node is replaced, and any state inside the inner component resets on every keystroke.
Fix: move the component to the top level of the module and pass what it needs as props.
Lint catches it: eslint-plugin-react has the rule react/no-unstable-nested-components.
Row stops being a component: React never sees it, so its useState becomes a hook of List, one per item. As soon as an item is added, React throws "Rendered more hooks than during the previous render" and the page goes blank.
Adding or removing items changes the number of hooks List calls; removing one fails with "Rendered fewer hooks than expected". Without an error boundary the whole root unmounts.
State follows the position, not the item: the open flags sit in List's hook list by index, so after a reorder the wrong row is open.
No key: the <li> comes back without one, and React warns "Each child in a list should have a unique "key" prop".
Bigger re-renders: setOpen in any row re-renders the whole List. memo cannot help either: calling a memo component throws TypeError: Row is not a function, because memo returns an object.
Fix: render an element, <Row key={item.id} item={item} />, and React creates an instance per row with its own state.
A plain call is fine for a helper that uses no hooks, such as renderPrice(cents); give it a lowercase name so nobody takes it for a component.
componentDidMount, componentDidUpdate and componentWillUnmount merge into one useEffect with a cleanup, shouldComponentUpdate becomes memo, and constructor state becomes useState. getSnapshotBeforeUpdate, getDerivedStateFromError and componentDidCatch have no hook.
**constructor**: initial state goes to useState, or its lazy form useState(() => compute()); nothing needs binding.
Mount, update, unmount: one effect covers all three: setup after mount, cleanup and setup again when a dependency changes, cleanup on unmount. The class needed a prevProps.roomId !== this.props.roomId check, and forgetting it was a classic bug.
Timing: componentDidMount and componentDidUpdate run before paint, like useLayoutEffect; useEffect usually runs after paint, which matters only when you measure the DOM.
**shouldComponentUpdate**: memo(Component, arePropsEqual), with the meaning flipped: arePropsEqual returns true to skip a render.
**getDerivedStateFromProps**: compute the value during render, or reset the state with a key.
No hook: getSnapshotBeforeUpdate (reading the scroll position right before the DOM changes) and error boundaries; the React docs say to keep a class for them.
React 19 (December 2024) made ref a regular prop, so forwardRef is no longer needed, and added Actions with useActionState, useFormStatus and useOptimistic, the use API, <Context> as a provider and <title> and <meta> support inside components.
**ref as a prop**: function components receive ref like any other prop; forwardRef still works but is on its way out.
Actions: pass an async function to <form action={...}> or startTransition, and React tracks the pending state, errors and the form reset.
**use(promise) and use(context)**: read a promise inside Suspense or a context; unlike hooks, use may be called conditionally.
**<ThemeContext value="dark">** replaces <ThemeContext.Provider>.
Document metadata: <title>, <meta> and <link> rendered anywhere are hoisted into <head>.
Ref callbacks may return a cleanup function.
React Compiler 1.0 (October 2025) is a separate build plugin that memoizes components automatically, so manual useMemo and useCallback are needed far less.
React ties state to a component's position in the tree. It keeps the state while the same component type renders at the same position with the same key, and resets it when the type at that position changes, the component is removed, or its key changes.
Same position, same type: {isAdmin ? <Form /> : <Form />} keeps the state, because both branches are the same Form at the same place.
Different type: switching a <div> wrapper to <section>, or <Form> to <Wizard>, destroys the state of the whole subtree.
Removed and re-added: {show && <Form />} resets the form every time it reappears.
**key as identity**: <ChatInput key={contactId} /> gives each contact its own instance, so switching contacts clears the draft without any reset effect.
Better than an effect that resets state when a prop changes: the effect first renders the stale value and then renders again.
To keep state across a removal, lift it up or store it outside the component, for example in a Map keyed by id or in a store.