In a React app, a text input loses focus after every keystroke and the surrounding widget's state resets. The code declares `function Field() { ... }` inside the parent component's body and renders `<Field />`. What is the mechanism, and how would you fix it?
answer
- what exactly is an element's type here
- declaration re-evaluated each call
- two identical functions are not equal
- remount, not re-render
- hoist it and pass props
basics
~20 sA component declared inside another component's body is a new function on every render, so the element type at that position differs each time and React remounts it — new DOM node, lost focus, reset state. Declare it at module scope instead.
solid answer
~40 sFor a component element, the `type` is the function reference itself. Declaring `Field` inside the parent's body creates a fresh function object on every render of the parent, so the element at that position has a type that never equals the previous one. React takes the different-type path: it unmounts the whole `Field` subtree — running effect cleanups, discarding hook state, destroying the DOM node and with it focus, caret and uncontrolled values — and mounts a brand-new one. It repeats on every parent render, which is why each keystroke costs the focus. The fix is to hoist `Field` to module scope so its identity is stable, and pass what it needs as props. If it needs to render parent-owned markup, pass that as `children` rather than closing over it.
code
jsx · 9 linesimport { useState } from 'react';
export function Parent() {
const [text, setText] = useState('');
function Field() {
return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
return <Field />;
}go deeper
Remember the rule of thumb: never declare a component inside another component. Declare it at module scope and pass data down as props.
Explain why it happens — the element's type is the function reference, a fresh declaration each render is a new reference, and a type mismatch rebuilds the subtree instead of updating it.
Diagnose it from symptoms in a real codebase: focus lost after one character, mount-only effects firing repeatedly, a widget re-initialising. Recognise the HOC and styled-factory variants, and justify hoisting over any reference-stabilising trick.
Make it unrepeatable rather than re-diagnosed: a lint rule against components declared in render, review guidance on factory calls, and a convention that anything wrapping a component happens once at module scope.
## The mechanism JSX for a component compiles to an element whose `type` is the component function. React's reconciler compares that type against the previous render's type at the same position. Function identity is the comparison. ```jsx function Parent() { const [text, setText] = useState(''); function Field() { // new function object every render return <input value={text} onChange={(e) => setText(e.target.value)} />; } return <Field />; // type differs from last render } ``` Every call of `Parent` evaluates the `function Field` declaration again and produces a *different* function object. Two function objects with identical source are not equal. So on the second render the element's type is not the previous type, React stops comparing, and it replaces the subtree. The replacement is a full unmount and mount: - effect cleanups run in the old subtree, then effects run again as mounts in the new one, - all `useState` and `useReducer` values are re-initialised, - the `<input>` DOM node is removed and a new one created, so `document.activeElement` no longer points at it and focus falls to `<body>`, - the caret offset, any text selection, scroll positions and uncontrolled input values in the subtree are gone, - refs detach from the old nodes and attach to the new ones. Typing one character sets state, `Parent` re-renders, `Field` is a new type, the input is destroyed and recreated, focus is lost. The controlled `value` is still correct — the letter you typed is there — which is what makes the bug confusing: the data flows fine and only the interaction breaks. ## Diagnosing it The symptom cluster is distinctive: focus lost after exactly one character, state inside the subtree resetting on unrelated parent updates, effects mounting repeatedly, a third-party widget or media element re-initialising constantly. In React DevTools the child appears as a new instance each time rather than an updating one, and any mount-only effect logs on every parent render. The same defect appears in disguises that are not a plain nested `function`: ```jsx function Parent({ data }) { const Styled = styled(Row); // new component every render const Wrapped = withTheme(Row); // same problem via an HOC return <Styled data={data} />; } ``` Anything that *creates* a component during render — a factory call, a wrapper applied inline, a component chosen out of a map that is rebuilt each render — produces a new type and remounts. Note the contrast with an inline arrow passed as a *prop*: `onClick={() => ...}` also creates a new function each render, but it is a prop value, not the element type, so it never causes a remount. ## The fix Hoist the component to module scope, where it is created once, and pass state through props: ```jsx function Field({ value, onChange }) { // stable identity return <input value={value} onChange={onChange} />; } export function Parent() { const [text, setText] = useState(''); return <Field value={text} onChange={(e) => setText(e.target.value)} />; } ``` Two variants worth knowing: - If the inner component was nested to close over parent state, props are the replacement. Closing over state was the convenience; passing it explicitly is the cost of a stable type. - If it was nested to inject parent-owned markup, pass that markup as `children` or as an element-valued prop. Elements can be created during render safely — it is *components* that must not be. Defining a component inside another is never the right call in production code. There is no configuration or memoization that makes it safe: wrapping the inner function in `useCallback` would stabilise the reference but still leaves a component defined per parent instance, and it obscures rather than fixes the problem. Hoisting is the answer. ## Why this question gets asked It is a single bug that requires the candidate to connect four things: how JSX encodes a component element, that function identity is the type comparison, that a type mismatch remounts rather than updates, and that focus is browser state living on a DOM node. Someone who can walk that chain understands reconciliation; someone who answers "add a key" or "memoize it" has pattern-matched instead.
- Why does an inline arrow function passed as onClick not cause the same remount?Because it is a prop value, not the element type. A new handler reference each render means React stores the new reference for dispatch, and at most defeats a memoized child's shallow prop comparison. Only the element's type decides between updating in place and rebuilding the subtree.
- Would wrapping the nested component in useCallback fix it?It would stabilise the reference for that instance, so the remounting stops, but it is the wrong fix. The component is still defined per parent instance, its identity is still tied to a hook's dependencies, and every reader has to verify that. Hoisting to module scope removes the hazard instead of pinning it.
- How does this show up when the inner component is produced by an HOC or a styled factory called during render?Identically. `withTheme(Row)` or `styled(Row)` evaluated inside the body returns a new component object each render, so the element type changes and the subtree remounts. Call such factories once at module scope and reference the result.
- Is there any case where you want a component created during render?Not in a render path. If a component genuinely must be built from configuration, build it outside render — at module scope, or once in a ref or a lazily initialised state slot — so a single identity survives across renders. Creating it fresh per render always means remounting.
saying these in an interview costs you the question
- Blames the inline onChange arrow for the focus loss
- Suggests adding a key to keep the input mounted
- Says React.memo or useMemo would prevent the remount
- Thinks two identically-written functions compare as equal
- Calls it a re-render problem rather than a remount