Skip to content

React

My React evidence: work UIs on the Model Portfolios platform, and the Prayog trading terminal (React 19, Redux Toolkit, TypeScript, Vite, WebSocket feeds, react-grid-layout workspace, Vitest + Testing Library).

Rendering model

What triggers a re-render?

A component re-renders when its state changes (setState with a new value), its parent re-renders (by default, children re-render too, even with the same props), or a context it reads changes. Props changing is a consequence of the parent re-rendering. Re-rendering means calling the function again; React then diffs and updates the DOM only where output changed.

What is the virtual DOM and reconciliation?

React keeps a tree of elements describing the UI. On re-render it builds a new tree and diffs it against the previous one: different element type → replace the subtree; same type → update props and recurse; lists matched by key. Then it commits the minimal DOM changes. Render phase (pure, can be interrupted in concurrent mode) vs. commit phase (DOM mutations, effects).

Why do keys matter? Why not use the index?

Keys tell React which list item is which between renders. With index keys, inserting at the top shifts every key, so React reuses the wrong component instances: state (input text, focus) sticks to the wrong row and work is wasted. Use a stable unique id. Index is fine only for static lists that never reorder.

Is setState synchronous? What is batching?

State updates are queued and applied on the next render, so reading state right after setting it gives the old value. React 18+ batches all updates in the same tick (event handlers, promises, timeouts) into one render. Use the updater form when the new state depends on the old: setCount(c => c + 1).

Controlled vs. uncontrolled components?

Controlled: React state is the source of truth (value + onChange); easy validation and dependent fields. Uncontrolled: the DOM holds the value, read via a ref or form submission; less code, fewer re-renders. React 19 form actions make uncontrolled forms more natural.

Hooks

What are the rules of hooks and why?

Call hooks only at the top level of a component or custom hook, never in loops, conditions or after an early return. React identifies each hook by its call order, so the order must be the same every render. The ESLint plugin react-hooks enforces it (Prayog uses it).

Explain useEffect: dependencies, cleanup, common mistakes.

Runs after the commit, for synchronising with something outside React (subscriptions, timers, network, DOM APIs). The dependency array controls when it re-runs; [] = on mount only. Return a cleanup that runs before the next effect and on unmount. Mistakes: missing dependencies (stale values), objects/functions recreated every render in the dependency array (infinite loops), fetching without handling races, and using an effect for things that should be computed during render ("you might not need an effect"). In dev, Strict Mode mounts, unmounts and mounts again to surface missing cleanups.

Prayog link: the market feed starts a WebSocket and returns a cleanup that stops reconnecting, clears the retry timer and closes the socket:

// apps/web/src/App.tsx: startMarketFeed returns its own cleanup function
function MarketFeed() {
  useEffect(() => startMarketFeed(store.dispatch, store.getState), [])
  return null
}
How do you fetch data in an effect without race conditions?

useEffect(() => {
  const ctrl = new AbortController()
  fetch(`/api/models/${id}`, { signal: ctrl.signal })
    .then(r => r.json())
    .then(setModel)
    .catch(e => { if (e.name !== 'AbortError') setError(e) })
  return () => ctrl.abort() // id changed or unmounted: ignore the old response
}, [id])
In real apps, use a data library (RTK Query, TanStack Query) for caching, deduplication, retries and loading states.

useMemo vs. useCallback vs. React.memo?

useMemo(fn, deps) caches a computed value; useCallback(fn, deps) caches a function identity. React.memo(Component) skips re-rendering when props are shallowly equal. They work together: memoising a child is useless if you pass it a new callback or object every render. Use them when profiling shows a cost, or to keep an identity stable for an effect's dependencies. The React Compiler (stable in 2025) auto-memoises, making most manual use unnecessary.

useRef: what is it for?

A mutable box (ref.current) that persists across renders without causing a re-render. Uses: DOM access (focus, measure, a chart library's container), holding a timer id, a WebSocket, or the latest value for a callback. React 19 lets function components take ref as a prop (no forwardRef).

useState vs. useReducer?

useReducer(reducer, init) when state has several related fields or the next state depends on the action type (forms, wizards); it centralises transitions and is easy to test. useState for independent simple values.

What are custom hooks? Give one you wrote.

Functions starting with use that compose hooks to share stateful logic (not state itself: each caller gets its own). Examples: useDebounce(value, ms), useWebSocket(url), useInterval. Prayog has typed store hooks: useAppSelector = useSelector.withTypes<RootState>().

Write useDebounce.
function useDebounce<T>(value: T, ms = 300): T {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), ms)
    return () => clearTimeout(t)
  }, [value, ms])
  return debounced
}
// const q = useDebounce(search); useEffect(() => { fetchResults(q) }, [q])

State management

Local state vs. Context vs. Redux: when to use which?

Local state first; lift it to the nearest common parent when siblings need it. Context for low-frequency global values (theme, signed-in user, locale): every consumer re-renders when the value changes, so it's poor for fast-changing data. Redux (Toolkit) for app-wide state that changes often, with many readers, where you want predictable updates, devtools and selectors that subscribe to slices. Server state (API data) belongs in a data-fetching cache (RTK Query, TanStack Query), not hand-written Redux.

Explain Redux and Redux Toolkit.

One store; state changes only by dispatching actions to pure reducers; components read with useSelector (re-render only when the selected value changes). Redux Toolkit removes the boilerplate: configureStore (devtools, thunk middleware), createSlice (reducers + action creators; Immer lets you "mutate" draft state safely), createAsyncThunk, createSelector (memoised), and RTK Query for API caching.

Prayog link: slices for market, account and strategies. The market slice applies WebSocket messages; each book update carries a seq, and if seq !== book.seq + 1 the book is marked stale, the feed closes the socket and resubscribes to get a fresh snapshot. That's the same gap-detection idea real market-data feeds use.

Why must reducers be pure and state immutable?

Redux and React detect changes by reference equality. Mutating in place keeps the same reference, so selectors and React.memo think nothing changed and the UI doesn't update. Purity makes reducers testable and enables time-travel debugging. Immer (in RTK) produces a new immutable state from "mutating" code.

How do you handle a high-frequency data stream (prices, order book) in React?

Keep the socket outside components (a module or middleware), normalise data in the store, select narrowly (one symbol's book, not the whole state), memoise derived data, batch or throttle updates to animation frames, render long lists virtualised, and let chart libraries update imperatively via refs instead of re-rendering React trees. For extreme rates, conflate on the server (send at most N updates/s).

Performance

How do you find and fix a slow React page?

Measure: React DevTools Profiler (which components re-render and why), Chrome Performance tab, Lighthouse / Web Vitals (LCP, INP, CLS). Fixes: stop unnecessary re-renders (move state down, split context, memoise), virtualise long lists (react-window), code-split routes with React.lazy + Suspense, debounce input-driven work, move heavy computation off the main thread (web worker), reduce bundle size.

What is code splitting?

Loading code only when needed: const Admin = lazy(() => import('./Admin')) inside <Suspense fallback=...>. Vite/Rollup creates a separate chunk. Split by route first.

Newer React

What did React 18 bring?

Concurrent rendering (React can pause and interrupt rendering), automatic batching everywhere, startTransition/useTransition (mark updates as non-urgent so typing stays responsive), useDeferredValue, Suspense for data on the server, createRoot.

What's new in React 19?

Actions (async functions in transitions, with pending state handled), useActionState, useFormStatus, useOptimistic (show the expected result before the server confirms), the use() API (read a promise or context during render), ref as a regular prop, document metadata (<title>) in components, and stable Server Components. Prayog's terminal runs on React 19.2.

What are React Server Components?

Components that run only on the server (or at build time), can read data directly, and send rendered output to the client with zero JS for themselves. Client components ('use client') handle interactivity. Used through frameworks (Next.js). Benefit: less JS shipped and data fetched close to the source.

How do error boundaries work?

A class component with static getDerivedStateFromError / componentDidCatch catches errors thrown while rendering its children and shows a fallback. It doesn't catch errors in event handlers, async code or effects after the fact (handle those with try/catch and state). Libraries like react-error-boundary give a hook-friendly API.

Testing

How do you test React components?

React Testing Library: render, find by role/label/text like a user would, fire events with user-event, assert on what's visible, not internal state. Mock network at the boundary (MSW). Unit-test reducers and pure helpers directly (Prayog: marketSlice.test.ts, depth.test.ts, candles.test.ts). E2E for key journeys with Playwright or Cypress.

Frontend security and auth

How do you store tokens in a SPA? XSS?

Prefer short-lived access tokens in memory with refresh via an HTTP-only, Secure, SameSite cookie, or a backend-for-frontend that keeps tokens server-side. localStorage is readable by any script, so an XSS steals the token. React escapes text by default; danger comes from dangerouslySetInnerHTML, href="javascript:..." and third-party scripts. Use a Content Security Policy.

Prayog link: the terminal signs in with OIDC (Authorization Code + PKCE via oidc-client-ts and Keycloak). Browsers can't set headers on WebSocket connections, so the private feed passes the token as a query parameter, which is why tokens are short-lived and a long-running strategy asks for a fresh one on each reconnect.