React (with Hooks, React 19 & Next.js basics)
In short: React is a JavaScript library (by Meta, 2013) for building user interfaces with reusable components.
- Features: declarative UI, component-based architecture, JSX, one-way data flow via props, state with hooks, efficient updates via reconciliation (virtual DOM diffing), a huge ecosystem, and React Native for mobile.
- It focuses on the view layer; routing, data fetching and SSR come from libraries or frameworks like Next.js.
In short: JSX is a syntax extension that looks like HTML inside JavaScript.
- A compiler (Babel, SWC, esbuild) turns it into function calls (
jsx("div", props)/React.createElement) that create React elements. - It supports embedding expressions in
{}, usesclassName/camelCase attributes, and escapes values by default to prevent XSS. - It's optional but universal.
In short: The virtual DOM is a lightweight in-memory tree of React elements describing the UI.
- On each render React creates a new tree, diffs it against the previous one (reconciliation) using heuristics — elements of a different type are replaced, same-type elements are updated, keys identify list items — and applies only the minimal changes to the real DOM in the commit phase.
- Fiber (React 16+) made this work interruptible and prioritised.
In short: Props are inputs passed from parent to child; they're read-only for the child.
- State is data owned and managed by a component (
useState/useReducer) that changes over time and triggers re-renders when updated. - A parent's state often becomes a child's props.
- Data flows down via props; children request changes via callback props.
In short: Keys give each list item a stable identity so React can match elements between renders and update, move or remove the right DOM nodes and preserve component state.
- Without good keys (or with array indexes when the list changes order), React may reuse the wrong component instance, causing bugs like inputs showing the wrong values and unnecessary re-renders.
- Use unique IDs from data; keys need to be unique only among siblings.
In short: In a controlled component, form input values are driven by React state (value + onChange), giving React full control — instant validation, conditional disabling, formatting.
- In an uncontrolled component, the DOM keeps the value and you read it when needed via refs or
FormData(defaultValue), which means less code and fewer re-renders (React Hook Form uses this approach). - File inputs are always uncontrolled.
In short: useEffect(fn, deps) runs side effects (data fetching, subscriptions, timers, DOM APIs) after the component commits.
- With no deps it runs after every render; with
[]once after mount; with[a, b]when those values change. - Return a cleanup function from the effect to unsubscribe, clear timers or abort fetches — React runs it before re-running the effect and on unmount.
- Missing dependencies cause stale values; the ESLint hooks rule helps.
In short: Only call hooks at the top level of React function components or custom hooks — never inside loops, conditions, nested functions or after early returns — and only from React functions.
- React stores hook state in an ordered list per component and relies on the call order being identical on every render to match each
useState/useEffectwith its stored data.
In short: useMemo(() => compute(a, b), [a, b]) caches the result of an expensive calculation.
useCallback(fn, deps)caches a function's identity so it doesn't change every render.React.memo(Component)skips re-rendering a component when its props are shallowly equal.- They work together: memoised child + stable callback/object props.
- Use them where profiling shows a benefit; the React Compiler now automates most of this.
In short: Context passes data through the component tree without prop drilling: createContext, a provider with a value, and useContext in consumers.
- It suits low-frequency global values (theme, auth user, locale, feature flags).
- Avoid it for frequently changing, large global state, because every consumer re-renders on any change of the value — use a state library (Zustand, Redux Toolkit) or split contexts and memoise values.
- It's not a replacement for server-state caching.
In short: useState is ideal for simple, independent values.
useReducercentralises complex state logic where the next state depends on the previous one and on multiple action types (forms with many fields, carts, wizards).- Its reducer is pure and easily testable, and
dispatchis stable, which helps memoisation. - It often pairs with Context for small app-wide stores.
In short: When two components need the same changing data, move the state to their closest common parent and pass it down as props, with callbacks for updates.
- This keeps a single source of truth.
- If many layers are involved, consider composition (passing components as children), Context, or a store.
In short: Functions starting with use that call other hooks to encapsulate reusable stateful logic — useFetch(url), useDebounce(value), useLocalStorage(key), useOnlineStatus().
- Each component that calls a custom hook gets its own independent state.
- They replace older patterns like HOCs and render props for logic reuse.
In short: Components (currently class components implementing getDerivedStateFromError/componentDidCatch, or the react-error-boundary package) that catch JavaScript errors in their child component tree during rendering, lifecycle methods and constructors, show a fallback UI and log the error, instead of unmounting the whole app.
- They don't catch errors in event handlers, async code (setTimeout, promises outside rendering), SSR or themselves.
In short: Profile with React DevTools;
- avoid unnecessary re-renders (state colocation, splitting components,
memowith stable props or the React Compiler, splitting contexts); - virtualise long lists;
- correct keys;
- code-split routes and heavy components with
lazy/Suspense; useTransition/useDeferredValuefor heavy updates during typing;- cache server data with TanStack Query;
- debounce inputs;
- use SSR/Server Components to reduce client JS;
- optimise images;
- always ship production builds.
In short: SSR renders the whole component tree to HTML on the server per request, then ships all the component JS to hydrate it in the browser.
- Server Components are components that run only on the server (or at build time): they can access databases and secrets directly, and their code is never sent to the browser — only their rendered output (RSC payload).
- Client Components (
"use client") are still hydrated for interactivity. - RSC and SSR are complementary — frameworks like Next.js use both.
In short: React 18: concurrent rendering (interruptible rendering), createRoot, automatic batching of all state updates, transitions (useTransition, startTransition), useDeferredValue, useId, streaming SSR with Suspense.
- React 19: Actions (async functions in transitions and
<form action>),useActionState,useFormStatus,useOptimistic, theuse()API,refas a prop (noforwardRef),<Context>as provider, document metadata tags, stable Server Components and Server Actions, and better hydration error messages. - 19.2 added
<Activity>anduseEffectEvent.
In short: Setting state during render unconditionally (setCount(count + 1) in the body), calling a handler instead of passing it (onClick={handle()}), or an effect that sets state which changes its own dependencies (objects/arrays/functions recreated each render in the dependency array).
- Fix by moving updates into event handlers, passing function references, computing derived values during render instead of in effects, memoising dependency objects or depending on primitive fields, and guarding effect updates with conditions.
In short: Find what re-renders with the Profiler.
- Usually the search state lives too high (in the page) so every widget re-renders.
- Move the search state down into the search component or the part that uses it;
- wrap expensive widgets in
memoand pass stable props (or enable the React Compiler); - use
useDeferredValuefor the filtered results so typing stays responsive; - debounce the search before triggering data fetching;
- split a big context into smaller ones.
In short: Race condition: an older request resolves after a newer one and overwrites state.
- Fix by aborting the previous request in the effect cleanup (
AbortController), or ignoring stale responses with a flag (`let ignore = false; - return () => { ignore = true }`).
- Better: use TanStack Query/SWR or framework loaders, which key requests by parameters and handle caching, deduplication and races automatically.
In short: Feature-based folders (features/orders/{components,hooks,api,types}) plus shared components/ui, lib, hooks;
- a framework (Next.js) or Vite + React Router;
- TypeScript everywhere;
- server state in TanStack Query (or framework loaders), minimal global client state in Zustand/Redux Toolkit;
- a design system (Tailwind/shadcn or MUI);
- forms with React Hook Form + Zod;
- route-based code splitting;
- testing with Vitest + RTL + MSW and Playwright;
- ESLint (hooks rules) and Prettier;
- Storybook for shared components;
- CI with type checks, tests and bundle-size budgets.
overflow: hidden on the card. How do you fix it in React?In short: Render the modal with a portal (createPortal(<Modal />, document.body) or a dedicated #modal-root) so it's attached outside the card's DOM hierarchy while still part of the React tree (events and context still work).
- Manage focus trapping, Escape to close and
aria-modalfor accessibility — or use the native<dialog>element or an accessible library (Radix UI).