skip to content

You need a React Testing Library test to assert how a component behaves after one of its props changes. Why is calling render() a second time the wrong way to do that, and what do the rerender and unmount functions returned by render give you instead?

level: middleimportance: should knowfreq 52%

answer

  1. two renders, two containers
  2. mount path vs update path
  3. effect dependencies only matter on update
  4. same container, same instance
  5. teardown is assertable too

basics

~20 s

A second render() mounts a second copy in its own container, so both exist in the document and queries match twice. rerender() re-renders into the same container so the component updates instead of remounting, and unmount() tears it down so you can assert teardown.

solid answer

~50 s

Each `render` call creates its own container and its own React root. Calling it twice gives you two live copies of the component in `document.body`, so `screen` queries start matching two elements and throw — and worse, you tested a fresh mount, not an update. Prop changes are an update path: effects with dependency arrays, memoisation and derived-state logic all behave differently on re-render than on first mount, and remounting hides exactly the bugs you were trying to find. `rerender(ui)` renders the new element into the same container with the same root, so React reconciles it as an update — the same instance, the same DOM nodes where possible. That is what you want for "prop went from loading to loaded". `unmount()` is the other half of the lifecycle: it removes the tree and runs teardown, which lets you assert that the component actually cleaned up — removed its listener, cleared its interval, aborted its work.

go deeper

for a junior

Know that render mounts a new copy every time it is called, and that the returned rerender function is how you change props on the component already on screen.

for a middle

Explain why the distinction matters: dependency-gated effects, memoised values and transition logic only run on update, so a remount silently skips the code path the test was aimed at.

for a senior

Demonstrate using unmount as an assertion — proving listeners, intervals and subscriptions are released — and recognise duplicate-mount smells in review before they become flaky ambiguous-query failures.

for a principal

Frame it as coverage strategy: mount, update and teardown are three distinct behaviours, and a suite that only ever mounts has a systematic blind spot that no amount of extra mount-only tests will close.

## Two renders are two components `render` is a mount operation. It builds a container `div`, appends it to `document.body`, creates a React root and mounts the tree. Nothing about the second call is aware of the first. After ```js render(<Toggle on={false} />) render(<Toggle on={true} />) ``` the document contains two containers, two `Toggle` instances, and two of every element inside them. A `screen.getByRole('switch')` now matches twice and throws. Candidates often "fix" that with `getAllBy...[1]`, which papers over the real problem: the second component never experienced a prop change at all. It mounted fresh with `on={true}`. ## Mount and update are different code paths That distinction is the substance of the question. In a component, first mount and subsequent update run different things: - an effect with a dependency array runs on mount, and on update only when a dependency changed — plus its cleanup runs before the next run; - derived state, memoised values and cached callbacks recompute only when their inputs change; - a controlled/uncontrolled switch, a scroll-position restore, an animation triggered by a value transition — all of them are transition behaviour that simply does not exist on a fresh mount; - teardown logic never runs at all if you never unmount. So a test that remounts is testing "what does this component look like when it starts in state B", while the behaviour under test was "what does this component do when it goes from A to B". Those are genuinely different assertions, and real defects live in the transition: a stale value that never refreshes because a dependency was omitted, a subscription that is not re-established when the id prop changes, a form that keeps the previous item's input when you navigate to the next item. ## rerender `rerender` comes back from the render result and re-renders into the same container: ```js const { rerender } = render(<UserCard userId="1" />) expect(screen.getByRole('heading')).toHaveTextContent('Ada') rerender(<UserCard userId="2" />) expect(screen.getByRole('heading')).toHaveTextContent('Grace') ``` React reconciles the new element against the existing tree, so the component instance survives, DOM nodes are reused where the shape matches, and the update path runs — which is precisely what makes the second assertion meaningful. If `UserCard` forgot `userId` in its effect dependencies, this test fails and a remount-based test would not. One detail worth knowing: `rerender` re-applies any `wrapper` you passed to `render`, so a component mounted inside a provider stack stays inside it. You pass the bare element, not the wrapped one. ## unmount `unmount()` removes the tree and runs React's teardown. Two uses: 1. **Asserting cleanup.** Effects that add a document listener, open a socket, start an interval or set a body class must undo that on unmount. This is a common production leak and an easy test: ```js const { unmount } = render(<EscapeToClose onClose={onClose} />) unmount() fireEvent.keyDown(document, { key: 'Escape' }) expect(onClose).not.toHaveBeenCalled() // the listener was removed ``` 2. **Controlling the point of teardown** inside a test that also asserts on what happens after, rather than leaving it to the automatic cleanup that runs when the test ends. ## How to choose - Behaviour when a prop changes → `rerender`. - Behaviour driven by the user → interaction, not a re-render; the component re-renders itself from its own state. - Behaviour on teardown → `unmount`, then assert the side effect is gone. - A genuinely independent scenario → a separate `test`, not a second `render` in the same one. Two mounts in one test is almost always a smell: either you wanted `rerender`, or you wanted two tests. ## The transferable principle Component test harnesses in every framework distinguish mounting from updating from destroying. Whatever library you use, know which of the three your assertion is really about, and drive that specific transition — because mount-only tests systematically miss the update and teardown bugs that dominate real bug reports.

  • With a wrapper passed to render, do you have to wrap the element yourself when calling rerender?
    No. `rerender` re-applies the same `wrapper` the original `render` used, so you pass the bare element and the provider stack stays in place. Wrapping it again would nest a second copy of the providers, which usually breaks context assumptions or silently gives the component the inner instance.
  • What kind of bug does a rerender-based test catch that a remount-based test cannot?
    Anything tied to a value transition: an effect missing a dependency so it never re-subscribes when an id prop changes, derived state cached from the first render, a form that keeps the previous record's input, or an animation that only fires on change. A remount starts clean and always looks correct.
  • When is a second render() call in one test actually reasonable?
    Almost never — but rendering two different components on purpose, for example asserting that two independent widgets do not interfere through a shared document-level listener, is a legitimate case. Even then, scope queries to each container rather than to the whole body, since screen now sees both.

saying these in an interview costs you the question

  • Calls render twice and uses getAllBy[1] to disambiguate
  • Thinks a second render replaces the first component
  • Assumes mount and update run identical code
  • Believes rerender needs the wrapper re-applied manually
  • Never unmounts, so teardown bugs are untestable

context