I stumbled upon an example of resetting state in the React docs: https://react.dev/learn/preserving-and-resetting-state#option-1-rendering-a-component-in-different-positions
I simplified my understanding of it into different sandboxes, and I can’t reconcile (pun not intended) what I see.
All of them feature the same implementation of Counter:
function Counter() {
const [score, setScore] = useState(0);
useEffect(() => {
const i = setInterval(() => setScore((n) => n + 1), 200);
return () => clearInterval(i);
}, []);
return <div>{score}</div>;
}
A Counter, once mounted, quickly counts up. If dismounted and remounted, the state is lost and reset to 0.
First, I wrap the Counter in a stateful container: Codesandbox
export default function Scoreboard() {
const [bool, setBool] = useState(true);
return (
<div>
{bool && <Counter />}
{!bool && <Counter />}
<button
onClick={() => {
setBool(!bool);
}}
>
Switch
</button>
</div>
);
}
When I click “Switch”, it resets the state of the Counter. I refactored it into something more “Obvious” which maintains this behavior: Codesandbox
export default function Scoreboard() {
const [bool, setBool] = useState(false);
return (
<div>
{bool ? (
<>
<div />
<Counter />
</>
) : (
<>
<Counter />
<div />
</>
)}
<button
onClick={() => {
setBool(!bool);
}}
>
Switch
</button>
</div>
);
}
My takeaway here is that regardless of the “Type” of component, if the position changes (from first to second), then the state is not carried over. So I wondered why I never encountered this before, given that it’s common to write components which have dynamic maps followed by stateful elements.
I wrote an example to test it: Codesandbox
export default function Scoreboard() {
const [count, setCount] = useState(0);
return (
<div>
<>
{Array(count)
.fill(0)
.map(() => (
<div>a</div>
))}
<Counter />
</>
<button
onClick={() => {
setCount((n) => n + 1);
}}
>
Switch
</button>
</div>
);
}
Now very surprisingly, despite the position of Counter changing on every click to Switch, the state is never reset.
Why is this happening? Why is state not preserved in the first two examples, but is in the third.
In all cases, the only thing changing from the perspective of React is the position of the Counter relative to its siblings.
Footnote: In all cases, the key is unspecified and defaults to the index. Reminder: calling an element in JSX in separate positions does NOT give them separate identities. Otherwise the state would not be reset here: https://codesandbox.io/s/counterreset-1-identity-7rxrc2?file=/App.js:127-158
Additionally, I know that giving Counter a key would fix this. I don’t want to “fix” it, I want to understand why the behavior differs.