Episode 2 — React Frontend Architecture NextJS / 2.5 — Event Handling and Conditional Rendering

Interview Questions: Event Handling & Conditional Rendering

How to use this material:

  1. Read each question and try answering it out loud before reading the model answer.
  2. Pay attention to the "Why interviewers ask" section — understand what they're really testing.
  3. Practice explaining with concrete examples, not just definitions.
  4. Time yourself — aim for 1-2 minute answers for beginner, 2-3 for advanced.
  5. The best answers combine theory with practical experience.

Beginner (Q1–Q6)

Q1. What is the difference between a SyntheticEvent and a native browser event in React?

Why interviewers ask: Tests foundational understanding of how React handles events internally.

Model answer: React wraps native browser events in SyntheticEvent objects that provide a consistent, cross-browser API. When you write onClick, React doesn't attach a listener to that specific DOM element — it uses a single event listener at the root of your application and delegates events through its own system. The SyntheticEvent has the same interface as native events (preventDefault(), stopPropagation(), target, etc.), but works identically across all browsers. You can access the underlying native event via event.nativeEvent. In React 17+, event pooling was removed, so you can safely use the event asynchronously.

Q2. What happens if you write onClick={handleClick()} instead of onClick={handleClick}?

Why interviewers ask: This is the #1 most common React event handling mistake. Tests attention to detail.

Model answer: onClick={handleClick()} calls handleClick immediately during render, not on click. The parentheses invoke the function, so it runs every time the component renders. If handleClick calls setState, you get an infinite re-render loop. The correct syntax is onClick={handleClick} (passing a reference) or onClick={() => handleClick(arg)} if you need to pass arguments. This is a fundamental JavaScript concept — functions are first-class values and you pass them by reference, not by invocation.

Q3. What is a controlled component in React?

Why interviewers ask: Controlled components are a core React concept for form handling.

Model answer: A controlled component is a form element whose value is driven by React state. You set the input's value prop to a state variable and update that state via an onChange handler on every keystroke. React becomes the "single source of truth" for the input's value. The opposite is an uncontrolled component, where the DOM manages its own state (using defaultValue and ref). Controlled components are preferred because they enable real-time validation, formatting as you type, conditional field behavior, and easier testing. The exception is file inputs, which are always uncontrolled because their value is read-only for security.

Q4. What are the main conditional rendering patterns in React?

Why interviewers ask: Tests breadth of knowledge about React's rendering capabilities.

Model answer: React uses plain JavaScript for conditional rendering — there are no special directives like Vue's v-if. The main patterns are: (1) && operator for show/hide — {isLoggedIn && <Dashboard />}, (2) ternary for A/B — {isLoggedIn ? <Dashboard /> : <Login />}, (3) early return for guard clauses — if (isLoading) return <Spinner />, (4) if/else with variable assignment before the return, (5) switch statement for multiple branches, and (6) object lookup — const pages = { home: <Home />, about: <About /> }; return pages[page]. The object lookup pattern is especially clean for mapping values to components.

Q5. Why is {count && <span>{count}</span>} a common bug when count is 0?

Why interviewers ask: Tests understanding of JavaScript truthiness and JSX rendering rules.

Model answer: When count is 0, 0 && <span>0</span> evaluates to 0 (JavaScript short-circuit returns the left side when it's falsy). JSX renders numbers as text, so React displays "0" on screen instead of rendering nothing. Other falsy values like false, null, and undefined render nothing, but 0 and NaN are rendered as text. Fixes include: {count > 0 && <span>...</span>}, {!!count && <span>...</span>}, {Boolean(count) && <span>...</span>}, or {count ? <span>...</span> : null}.

Q6. What's the difference between event.preventDefault() and event.stopPropagation()?

Why interviewers ask: Tests understanding of event flow and practical debugging skills.

Model answer: preventDefault() cancels the browser's default action for that event — for example, preventing a form from submitting and reloading the page, or preventing a link from navigating. stopPropagation() stops the event from bubbling up to parent elements. A classic use case for stopPropagation is a delete button inside a clickable card — clicking the button should only trigger delete, not also trigger the card's click handler. You can use both together. preventDefault doesn't stop bubbling, and stopPropagation doesn't cancel defaults.


Intermediate (Q7–Q12)

Q7. How do you handle forms with many fields without writing a separate handler for each?

Why interviewers ask: Tests practical React patterns and code organization.

Model answer: Use a single state object and a single handleChange function that reads the input's name attribute. Store all field values in one state object: const [form, setForm] = useState({ firstName: '', email: '', role: '' }). The handler uses computed property names: function handleChange(e) { const { name, value, type, checked } = e.target; setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }. Each input just needs name={fieldName} value={form[fieldName]} onChange={handleChange}. This scales to any number of fields. For even more complex forms, you'd extract this into a custom useForm hook or use libraries like React Hook Form.

Q8. When would you choose to hide a component with CSS (display: none) versus conditional rendering ({show && <Component />})?

Why interviewers ask: Tests understanding of component lifecycle, state preservation, and performance trade-offs.

Model answer: Conditional rendering ({show && <Component />}) unmounts the component — its state is destroyed, cleanup functions run, and it's removed from the DOM. CSS hiding keeps the component mounted — state is preserved, effects don't re-run, but it still consumes memory. Use conditional rendering when: the component is heavy and shouldn't waste resources when hidden, you want a fresh state each time it appears (like a modal), or the component has side effects that should stop. Use CSS hiding when: you want to preserve state across toggles (like tab content with form data), the component is expensive to mount (fetches data, initializes libraries), or you're animating show/hide transitions. A common pattern is tabs — render all tab content but display: none the inactive ones to preserve their state.

Q9. How do you implement a modal in React with proper UX?

Why interviewers ask: Tests ability to handle complex UI patterns including portals, event handling, accessibility.

Model answer: A proper modal needs several things: (1) Use createPortal to render outside the component tree, directly into document.body, avoiding CSS stacking context issues. (2) Show a backdrop overlay — clicking it should close the modal. (3) Use event.stopPropagation() on the modal content so clicking inside doesn't trigger the backdrop's close handler. (4) Listen for Escape key to close: useEffect with a keydown listener. (5) Lock background scrolling: set document.body.style.overflow = 'hidden' when open, restore on cleanup. (6) For accessibility: role="dialog", aria-modal="true", aria-labelledby pointing to the title, focus trapping inside the modal, and restoring focus to the trigger element on close. These are all handled in useEffect with proper cleanup.

Q10. How does React event delegation differ from manual event delegation in vanilla JavaScript?

Why interviewers ask: Tests deep understanding of React's event system architecture.

Model answer: In vanilla JavaScript, you manually attach one listener to a parent element and use event.target to determine which child was clicked — this is event delegation as a performance optimization. React does this automatically. Since React 17, React attaches a single event listener to the root DOM node of your app (the #root div), not to document like in React 16. All events bubble up to this root listener, where React determines which component's handler to call based on the Fiber tree. This means you never need to manually delegate events in React — writing onClick on any element works efficiently regardless of how many elements have handlers. The benefit of React 17's change (root vs document) is better compatibility with multiple React roots and micro-frontends.

Q11. What is the difference between onMouseEnter and onMouseOver in React?

Why interviewers ask: Tests nuanced understanding of event behavior and common source of UI bugs.

Model answer: onMouseEnter fires once when the mouse enters the element and does NOT bubble — entering a child element doesn't trigger it again. onMouseOver fires when entering the element AND again when entering any child element, and it bubbles. For hover states (like changing a card's background on hover), onMouseEnter/onMouseLeave is almost always what you want because it gives clean, predictable behavior. onMouseOver/onMouseOut can cause flickering when the mouse moves between parent and child elements, because each crossing triggers the event. The same distinction applies to onMouseLeave vs onMouseOut.

Q12. How do you handle real-time form validation that doesn't annoy users?

Why interviewers ask: Tests UX awareness and practical form design skills.

Model answer: The best pattern is validate on blur, re-validate on change. When a user is typing in a field for the first time, don't show errors — they haven't finished yet. When they leave the field (blur), validate and show errors if any. Once a field is "touched" and has an error, re-validate on every keystroke so the error message disappears as soon as they fix it. This requires tracking a touched state object alongside values and errors. For multi-step forms, validate all fields in the current step when the user clicks "Next" — mark all step fields as touched. For password fields specifically, a real-time strength meter is acceptable because it's positive feedback, not error display.


Advanced (Q13–Q18)

Q13. Compare the performance implications of different approaches to passing arguments to event handlers in a large list.

Why interviewers ask: Tests performance awareness and practical optimization knowledge.

Model answer: Three approaches with different performance profiles: (1) Arrow function wrapper onClick={() => handle(id)} creates a new function on every render for every list item. If items are wrapped in React.memo, this breaks memoization because the function reference changes. (2) Currying onClick={handle(id)} has the same issue — new function each render. (3) Data attributes data-id={id} onClick={handleClick} passes a single stable function reference to all items, reading the ID from event.currentTarget.dataset.id. For lists under 100 items, the difference is negligible — use whichever is most readable. For large lists (500+), combine React.memo with useCallback for the handler, or use data attributes for zero extra functions. Always profile first with React DevTools Profiler rather than prematurely optimizing.

Q14. Design a form architecture that supports dynamic field schemas loaded from a server.

Why interviewers ask: Tests system design thinking and ability to build extensible form systems.

Model answer: Build a config-driven form system: The server sends a JSON schema defining fields, their types, validation rules, and layout. Create a field type registry mapping type strings to React components: { text: TextInput, select: SelectInput, checkbox: CheckboxGroup }. The form engine iterates over the schema, resolves each field's component from the registry, and renders it with standardized props (value, onChange, onBlur, error). Validation rules are also schema-driven — the server specifies required, minLength, pattern, etc., and a validation engine maps these to validator functions. State is managed in a single object keyed by field names. This approach means adding new field types requires only registering a new component, and form changes are deployed as config updates, not code changes. Libraries like React Hook Form with Zod schemas work well for this.

Q15. How would you implement a keyboard shortcut system across a React application?

Why interviewers ask: Tests understanding of global event handling, cleanup, and hook design.

Model answer: Create a useKeyboardShortcut(key, handler, modifiers) custom hook that attaches a keydown listener to window via useEffect. The hook checks if the pressed key matches (accounting for case), and if required modifiers (Ctrl/Cmd, Shift, Alt) are held. Use event.preventDefault() to override browser defaults (like Ctrl+S). The cleanup function removes the listener on unmount. For complex apps, create a ShortcutProvider context that registers all shortcuts centrally, handles conflicts (same key in different contexts), and provides a UI to display available shortcuts (like a command palette). Key decisions: use event.key for character-based shortcuts (locale-aware) or event.code for physical-key shortcuts (games). Support both Ctrl (Windows) and Cmd (Mac) via event.metaKey || event.ctrlKey. Disable shortcuts when focus is in text inputs to avoid conflicts.

Q16. Explain the rendering behavior when you use conditional rendering inside a list with keys.

Why interviewers ask: Tests deep understanding of React's reconciliation algorithm.

Model answer: React uses keys to maintain identity of list items across renders. If you conditionally render items (filter some out), React uses keys to determine which items were added, removed, or reordered. Without keys (or with index keys), React can't tell the difference between "item removed from the middle" and "all items after it shifted up," leading to incorrect state preservation and DOM mutations. With stable keys, React correctly: (1) unmounts removed items and runs their cleanup, (2) preserves state for items that remain (even if their position changes), and (3) mounts new items fresh. A subtlety: if you conditionally render different component types at the same position without a key, React destroys and recreates the entire subtree because the element type changed. Adding a key to force re-mount (key={selectedId}) is a useful pattern when you want fresh state.

Q17. How do you handle form state that depends on other field values (like cascading dropdowns)?

Why interviewers ask: Tests ability to handle complex interdependent state.

Model answer: Cascading dropdowns (Country → State → City) require dependent state management. When the parent field changes, reset child fields and fetch new options. Three approaches: (1) useEffect watching the parent value — when country changes, reset state to empty, fetch new state options. (2) In the handleChange function itself — when name === 'country', also set state: '' and city: ''. Approach 2 is preferred because it's synchronous and avoids the extra render cycle from useEffect. For validation, child fields should only validate when their parent has a value. For the options, either fetch on parent change or pre-load all options and filter client-side. Use useReducer instead of useState for complex interdependent form state — reducers make it explicit which fields reset together and prevent impossible states.

Q18. Critique this conditional rendering approach and suggest improvements:

{isLoading ? <Spinner /> : error ? <Error msg={error} /> : data?.length > 0 ? <List data={data} /> : <Empty />}

Why interviewers ask: Tests code quality awareness and refactoring skills.

Model answer: This nested ternary has several problems: it's hard to read, hard to maintain, easy to introduce bugs when adding states, and the nesting makes the priority/ordering non-obvious. Better alternatives: (1) Early returns — if (isLoading) return <Spinner />; if (error) return <Error />; if (!data?.length) return <Empty />; return <List />;. This reads top-to-bottom with clear priority. (2) State machine approach — model the component state as a discriminated union: status: 'loading' | 'error' | 'empty' | 'data' and switch on it. This prevents impossible states (like having both loading and error true). (3) Extracted function — function renderContent() { ... } with early returns, called in JSX as {renderContent()}. The key principle: conditional rendering should be flat (not nested) and read in priority order. The early return pattern is the React community standard for loading/error/empty/data states.


Review your answers against 2.5 sub-topic files for detailed explanations.