Practice12 min read · Updated 2025-06-01

Hard React Output & Behaviour Questions (With Step-by-Step Explanations)

The trickiest React behaviour questions from real interviews — useState batching, stale closures, re-render triggers, key resets, and useEffect timing.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Hard React Output & Behaviour Questions

Question 1: useState — What's the Output?

function Counter() {
  const [count, setCount] = useState(0)

function handleClick() { setCount(count + 1) setCount(count + 1) setCount(count + 1) }

return <button onClick={handleClick}>{count}</button> }

Q: After clicking once, what is the count?

Answer: 1

All three setCount(count + 1) calls read the same count = 0 from the closure. All three schedule "set to 0+1 = 1". React batches them into one re-render with count = 1.

Fix: functional updates

setCount(prev => prev + 1) setCount(prev => prev + 1) setCount(prev => prev + 1) // Result: 3 — each update builds on the previous

---

Question 2: useEffect Timing

function App() {
  const [val, setVal] = useState(0)

console.log('render', val)

useEffect(() => { console.log('effect', val) })

return <button onClick={() => setVal(v => v + 1)}>Click</button> }

Q: What's the console output on initial load, then after one click?

Answer:

Initial load:

render 0 effect 0

After click:

render 1 effect 1

useEffect runs after render and paint. The console.log in the component body runs during render. No dependency array → runs after every render.

---

Question 3: Stale Closure

function Timer() {
  const [count, setCount] = useState(0)

useEffect(() => { const id = setInterval(() => { setCount(count + 1) }, 1000) return () => clearInterval(id) }, [])

return <div>{count}</div> }

Q: What happens after 5 seconds?

Answer: count stays at 1 forever

The effect runs once (empty deps). The interval callback captures count = 0 in its closure. It will always call setCount(0 + 1) — setting count to 1 on every tick.

Fix:

useEffect(() => {   const id = setInterval(() => {     setCount(prev => prev + 1)  // functional update — no closure needed   }, 1000)   return () => clearInterval(id) }, [])

---

Question 4: Key as Reset

function App() {
  const [version, setVersion] = useState(0)
  return (
    <>
      <Form key={version} />
      <button onClick={() => setVersion(v => v + 1)}>Reset Form</button>
    </>
  )
}

function Form() { const [input, setInput] = useState('') return <input value={input} onChange={e => setInput(e.target.value)} /> }

Q: What happens when the Reset button is clicked?

Answer: Form completely resets to empty string

Changing the key makes React treat

as a completely different component. It unmounts the old Form (destroying its state) and mounts a brand new one with input = ''.

This is the intentional use of key to reset a component without lifting state up.

---

Question 5: Re-render Triggers

const Parent = () => {
  const [count, setCount] = useState(0)
  console.log('Parent rendered')
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <Child />
    </div>
  )
}

const Child = () => { console.log('Child rendered') return <p>I am a child</p> }

Q: What logs on every button click?

Answer: Both "Parent rendered" and "Child rendered"

React re-renders Parent when count changes. Child is a child of Parent, so React re-renders Child too — even though Child receives no props at all. Children re-render with their parent by default.

Fix: React.memo

const Child = React.memo(() => {   console.log('Child rendered')  // only logs once — on mount   return <p>I am a child</p> })

---

Question 6: useEffect Dependency Bug

function Search({ query }) {
  const [results, setResults] = useState([])

useEffect(() => { fetch('/api/search?q=' + query) .then(r => r.json()) .then(data => setResults(data)) }, []) // ← empty deps

return results.map(r => <div key={r.id}>{r.name}</div>) }

Q: What's wrong with this component?

Answer: query is stale — the effect only runs on mount

When the query prop changes (parent types a new search term), the component re-renders but the effect doesn't re-run. The displayed results are from the initial query forever.

Fix: Add query to the dependency array:

useEffect(() => {   let cancelled = false   fetch('/api/search?q=' + query)     .then(r => r.json())     .then(data => { if (!cancelled) setResults(data) })   return () => { cancelled = true } }, [query])  // re-fetch whenever query changes

---

Question 7: Context Re-render

const Ctx = createContext(null)

function Provider() { const [count, setCount] = useState(0) return ( <Ctx.Provider value={{ count, setCount }}> <Consumer /> <button onClick={() => setCount(c => c + 1)}>+</button> </Ctx.Provider> ) }

function Consumer() { const { setCount } = useContext(Ctx) console.log('Consumer rendered') return <button onClick={() => setCount(0)}>Reset</button> }

Q: Does Consumer re-render when count changes?

Answer: Yes — on every count change

{ count, setCount } is a new object reference every render. useContext performs reference equality: different object = all consumers re-render, even if Consumer only reads setCount.

Fix: memoize the value

const value = useMemo(() => ({ count, setCount }), [count]) <Ctx.Provider value={value}>

Now Consumer only re-renders when count actually changes (which it does in this case — but with more fields, only the relevant consumers would re-render).

---

Question 8: The Async State Update

function App() {
  const [data, setData] = useState(null)

async function fetchData() { const result = await fetch('/api/data').then(r => r.json()) setData(result) console.log(data) // null or result? }

return <button onClick={fetchData}>Fetch</button> }

Q: What does console.log(data) print after the await?

Answer: null (the old value)

data in the closure is captured at the time fetchData was created — when data = null. setData(result) schedules a state update (re-render), but the closure's data variable is not updated. The new value is only available in the next render's closure.

Practice React behavior questions at [JSPrep Pro](/auth).

📚 Practice These Topics
UseState
4–8 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read