🔹 What is useState with Objects?
In React, useState can store objects when you need to manage multiple related values together (like form data, user info, settings, etc.).
Instead of creating many separate states, you store them inside one object.
🔹 Basic Syntax
const [state, setState] = useState({
key1: value1,
key2: value2
});state→ current object statesetState→ function to update the object⚠️ Updating replaces the entire object (not merges)
🔹 Simple Example
<div class="infographic-userprofileflow" id="uf">
<style>
.infographic-userprofileflow {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
padding: 1rem;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-userprofileflow[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
}
.infographic-userprofileflow * { box-sizing: border-box; min-width: 0; }
.uf-eyebrow {
font-family: var(--mono-font); font-size: 0.72rem; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.45rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.uf-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.uf-title {
font-family: var(--display-font); font-size: clamp(1.5rem, 4vw, 2.3rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.12; letter-spacing: -0.01em;
}
.uf-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.9rem; }
.uf-grid { display: grid; grid-template-columns: 1.3fr 1fr; gap: 1.5rem; align-items: start; margin-bottom: 1.3rem; }
@media (max-width: 720px) { .uf-grid { grid-template-columns: 1fr; } }
/* ===== code panel ===== */
.uf-code-head { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.uf-code-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.uf-code-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.uf-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem 0.6rem; overflow-x: auto;
}
.uf-code-block code { font-family: var(--mono-font); font-size: 0.74rem; line-height: 1.75; color: #d4d4d8; white-space: pre; }
.uf-line {
display: block; padding: 0 0.6rem; margin: 0 -0.6rem; border-left: 2px solid transparent;
border-radius: 3px; transition: background 0.25s ease, border-color 0.25s ease;
}
.uf-line.is-current { background: rgba(37,99,235,0.2); border-left-color: var(--accent); }
.tok-kw { color: #c678dd; }
.tok-tag { color: #e06c75; }
.tok-type { color: #61afef; }
.tok-attr { color: #d19a66; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.tok-punc { color: #6b7280; }
/* ===== output panel ===== */
.uf-output-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; }
.uf-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
.uf-preview {
border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; width: 100%; max-width: 260px;
box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18);
}
.uf-preview-bar { background: #e9e9e9; padding: 0.45rem 0.6rem; display: flex; gap: 0.3rem; }
.uf-preview-bar span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.uf-preview-bar span:nth-child(1) { background: #ff5f56; }
.uf-preview-bar span:nth-child(2) { background: #ffbd2e; }
.uf-preview-bar span:nth-child(3) { background: #27c93f; }
.uf-preview-page { background: #fff; padding: 1.5rem 1.2rem; text-align: center; font-family: Times, "Times New Roman", serif; }
.uf-preview-page p { margin: 0 0 0.6rem; font-size: 1.02rem; color: #000; }
.uf-preview-btn {
position: relative; font-family: var(--body-font); font-size: 0.85rem; font-weight: 600; color: #000;
background: #efefef; border: 2px outset #ccc; padding: 0.4rem 0.9rem; cursor: pointer; margin-top: 0.5rem;
}
.uf-preview-btn:disabled { color: #999; cursor: default; opacity: 0.7; }
.uf-click-ping {
position: absolute; inset: -8px; border: 2px solid var(--accent); border-radius: 10px;
opacity: 0; pointer-events: none;
}
.uf-preview-page.is-clicking .uf-click-ping { animation: uf-ping 0.6s ease-out; }
@keyframes uf-ping { 0% { opacity: 0.9; transform: scale(0.92); } 100% { opacity: 0; transform: scale(1.25); } }
.uf-hint { font-size: 0.72rem; color: var(--ink-soft); text-align: center; min-height: 1.4em; }
/* ===== progress + caption ===== */
.uf-caption {
text-align: center; font-size: 0.92rem; color: var(--ink); min-height: 2.6em; display: flex;
align-items: center; justify-content: center; max-width: 560px; margin: 0 auto 0.9rem; line-height: 1.45;
padding: 0 0.5rem;
}
.uf-dots { display: flex; justify-content: center; gap: 0.4rem; }
.uf-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); transition: background 0.2s, transform 0.2s; }
.uf-dot.is-current { background: var(--accent); transform: scale(1.3); }
.uf-dot.is-done { background: var(--accent-deep); }
.uf-takeaway {
margin-top: 1.7rem; padding: 0.95rem 1.2rem; background: var(--accent-soft);
border-left: 3px solid var(--accent); border-radius: 4px; font-size: 0.9rem; color: var(--ink); line-height: 1.5;
max-width: 620px; margin-left: auto; margin-right: auto;
}
.uf-takeaway b { color: var(--accent-deep); }
@media (prefers-reduced-motion: reduce) {
.infographic-userprofileflow *, .infographic-userprofileflow *::before, .infographic-userprofileflow *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="uf-eyebrow">Execution Flow</div>
<h1 class="uf-title">What Happens on Click</h1>
<p class="uf-sub">Click Update User in the output to watch it run.</p>
<div class="uf-grid">
<div class="uf-code-block">
<div class="uf-code-head">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3 L2 8 L6 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3 L14 8 L10 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="uf-code-name">UserProfile.jsx</span>
</div>
<pre><code><span class="uf-line" data-line="1"><span class="tok-kw">import</span> <span class="tok-punc">{</span> <span class="tok-type">useState</span> <span class="tok-punc">}</span> <span class="tok-kw">from</span> <span class="tok-str">"react"</span><span class="tok-punc">;</span></span><span class="uf-line" data-line="2"><span class="tok-kw">function</span> <span class="tok-type">UserProfile</span><span class="tok-punc">() {</span></span><span class="uf-line" data-line="3"> <span class="tok-kw">const</span> <span class="tok-punc">[</span>user<span class="tok-punc">,</span> setUser<span class="tok-punc">]</span> <span class="tok-punc">=</span> <span class="tok-type">useState</span><span class="tok-punc">({</span> <span class="tok-attr">name</span><span class="tok-punc">:</span> <span class="tok-str">"Alex"</span><span class="tok-punc">,</span> <span class="tok-attr">age</span><span class="tok-punc">:</span> <span class="tok-expr">25</span> <span class="tok-punc">});</span></span><span class="uf-line" data-line="4"> <span class="tok-kw">return</span> <span class="tok-punc">(</span></span><span class="uf-line" data-line="5"> <span class="tok-punc"><</span><span class="tok-tag">div</span><span class="tok-punc">></span></span><span class="uf-line" data-line="6"> <span class="tok-punc"><</span><span class="tok-tag">p</span><span class="tok-punc">></span>Name: <span class="tok-punc">{</span>user.name<span class="tok-punc">}</span><span class="tok-punc"></</span><span class="tok-tag">p</span><span class="tok-punc">></span></span><span class="uf-line" data-line="7"> <span class="tok-punc"><</span><span class="tok-tag">p</span><span class="tok-punc">></span>Age: <span class="tok-punc">{</span>user.age<span class="tok-punc">}</span><span class="tok-punc"></</span><span class="tok-tag">p</span><span class="tok-punc">></span></span><span class="uf-line" data-line="8"> <span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">onClick</span><span class="tok-punc">={() =></span> setUser<span class="tok-punc">({</span> <span class="tok-attr">name</span><span class="tok-punc">:</span> <span class="tok-str">"John"</span><span class="tok-punc">,</span> <span class="tok-attr">age</span><span class="tok-punc">:</span> <span class="tok-expr">30</span> <span class="tok-punc">})}></span></span><span class="uf-line" data-line="9"> Update User</span><span class="uf-line" data-line="10"> <span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span></span><span class="uf-line" data-line="11"> <span class="tok-punc"></</span><span class="tok-tag">div</span><span class="tok-punc">></span></span><span class="uf-line" data-line="12"> <span class="tok-punc">);</span></span><span class="uf-line" data-line="13"><span class="tok-punc">}</span></span></code></pre>
</div>
<div class="uf-output-wrap">
<div class="uf-output-label">Output</div>
<div class="uf-preview">
<div class="uf-preview-bar"><span></span><span></span><span></span></div>
<div class="uf-preview-page" id="ufPreviewPage">
<p id="ufName">Name: Alex</p>
<p id="ufAge">Age: 25</p>
<button class="uf-preview-btn" id="ufBtn" type="button">Update User<span class="uf-click-ping"></span></button>
</div>
</div>
<div class="uf-hint" id="ufHint">Click the button to run the code</div>
</div>
</div>
<p class="uf-caption" id="ufCaption">Click Update User to see what runs.</p>
<div class="uf-dots" id="ufDots"></div>
<div class="uf-takeaway"><b>setUser() replaces the whole object</b> — the new one has to carry every field you want to keep.</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('uf');
if (!root) return;
/* --- dark mode detection: body attribute > cookie > postMessage > light --- */
function applyDark(dark) {
if (dark) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
applyDark(dark);
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') applyDark(!!e.data.value);
});
/* --- click-driven execution playback --- */
var lineEls = root.querySelectorAll('.uf-line');
var caption = root.querySelector('#ufCaption');
var nameEl = root.querySelector('#ufName');
var ageEl = root.querySelector('#ufAge');
var previewPage = root.querySelector('#ufPreviewPage');
var btn = root.querySelector('#ufBtn');
var hint = root.querySelector('#ufHint');
var dotsWrap = root.querySelector('#ufDots');
var STEP_DELAY = 900;
var user = { name: 'Alex', age: 25 };
var playing = false;
function buildSteps(prevUser, nextUser) {
return [
{ lines: [2], caption: 'UserProfile() runs.', name: prevUser.name, age: prevUser.age, click: false },
{ lines: [3], caption: 'useState returns the current user object.', name: prevUser.name, age: prevUser.age, click: false },
{ lines: [6, 7], caption: 'JSX renders \u2014 shows Name: ' + prevUser.name + ', Age: ' + prevUser.age + '.', name: prevUser.name, age: prevUser.age, click: false },
{ lines: [8], caption: 'Click detected \u2192 setUser() replaces the object.', name: prevUser.name, age: prevUser.age, click: true },
{ lines: [2], caption: 'State changed \u2014 UserProfile() runs again.', name: prevUser.name, age: prevUser.age, click: false },
{ lines: [3], caption: 'useState now returns { name: "' + nextUser.name + '", age: ' + nextUser.age + ' }.', name: prevUser.name, age: prevUser.age, click: false },
{ lines: [6, 7], caption: 'New JSX renders \u2014 Name: ' + nextUser.name + ', Age: ' + nextUser.age + '.', name: nextUser.name, age: nextUser.age, click: false }
];
}
var dots = [];
for (var i = 0; i < 7; i++) {
var d = document.createElement('span');
d.className = 'uf-dot';
dotsWrap.appendChild(d);
dots.push(d);
}
function showStep(steps, index) {
var step = steps[index];
lineEls.forEach(function (el) {
var n = parseInt(el.getAttribute('data-line'), 10);
el.classList.toggle('is-current', step.lines.indexOf(n) !== -1);
});
caption.textContent = step.caption;
nameEl.textContent = 'Name: ' + step.name;
ageEl.textContent = 'Age: ' + step.age;
dots.forEach(function (d, i) {
d.classList.toggle('is-current', i === index);
d.classList.toggle('is-done', i < index);
});
previewPage.classList.remove('is-clicking');
if (step.click) {
void previewPage.offsetWidth;
previewPage.classList.add('is-clicking');
}
}
function playFlow() {
if (playing) return;
playing = true;
btn.disabled = true;
hint.textContent = 'Running\u2026';
var prevUser = user;
var nextUser = { name: 'John', age: 30 };
var steps = buildSteps(prevUser, nextUser);
var i = 0;
showStep(steps, 0);
var timer = setInterval(function () {
i++;
if (i >= steps.length) {
clearInterval(timer);
user = nextUser;
playing = false;
btn.disabled = false;
hint.textContent = 'Click the button to run the code';
dots.forEach(function (d) { d.classList.remove('is-current', 'is-done'); });
return;
}
showStep(steps, i);
}, STEP_DELAY);
}
btn.addEventListener('click', playFlow);
})();
</script>🧠 What’s happening?
userstores an object{ name, age }Clicking button replaces the entire object with a new one
🔹 Updating Only One Property (IMPORTANT)
When updating one property, you must copy the previous object using the spread operator (...).
❌ Wrong Way
setUser({ name: "John" });➡️ This removes age ❌
✅ Correct Way
setUser({
...user,
name: "John"
});➡️ Keeps age and updates name ✅
🔹 Example: Updating Individual Fields
<div class="infographic-profileform" id="pf">
<style>
.infographic-profileform {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
padding: 1rem;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-profileform[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
}
.infographic-profileform * { box-sizing: border-box; min-width: 0; }
.pf-eyebrow {
font-family: var(--mono-font); font-size: 0.72rem; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.45rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.pf-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.pf-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4vw, 2.15rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.18; letter-spacing: -0.01em;
}
.pf-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.9rem; }
.pf-grid { display: grid; grid-template-columns: 1.3fr 1fr; gap: 1.5rem; align-items: start; margin-bottom: 1.2rem; }
@media (max-width: 720px) { .pf-grid { grid-template-columns: 1fr; } }
/* ===== code panel ===== */
.pf-code-head { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.pf-code-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.pf-code-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.pf-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem 0.6rem; overflow-x: auto;
}
.pf-code-block code { font-family: var(--mono-font); font-size: 0.72rem; line-height: 1.7; color: #d4d4d8; white-space: pre; }
.pf-line {
display: block; padding: 0 0.6rem; margin: 0 -0.6rem; border-left: 2px solid transparent;
border-radius: 3px; transition: background 0.2s ease, border-color 0.2s ease;
}
.pf-line.is-current { background: rgba(37,99,235,0.2); border-left-color: var(--accent); }
.tok-kw { color: #c678dd; }
.tok-tag { color: #e06c75; }
.tok-type { color: #61afef; }
.tok-attr { color: #d19a66; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.tok-punc { color: #6b7280; }
/* ===== output panel ===== */
.pf-output-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; }
.pf-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
.pf-preview {
border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; width: 100%; max-width: 280px;
box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18);
}
.pf-preview-bar { background: #e9e9e9; padding: 0.45rem 0.6rem; display: flex; gap: 0.3rem; }
.pf-preview-bar span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.pf-preview-bar span:nth-child(1) { background: #ff5f56; }
.pf-preview-bar span:nth-child(2) { background: #ffbd2e; }
.pf-preview-bar span:nth-child(3) { background: #27c93f; }
.pf-preview-page { background: #fff; padding: 1.4rem 1.2rem; font-family: Times, "Times New Roman", serif; }
.pf-preview-page input {
display: block; width: 100%; font-family: inherit; font-size: 0.95rem; padding: 0.4rem 0.5rem;
border: 1px solid #999; margin-bottom: 0.7rem; box-sizing: border-box;
}
.pf-preview-page input:focus { outline: 2px solid var(--accent); border-color: var(--accent); }
.pf-preview-page p { margin: 0.2rem 0 0; font-size: 1rem; color: #000; min-height: 1.4em; }
.pf-hint { font-size: 0.72rem; color: var(--ink-soft); text-align: center; min-height: 1.4em; max-width: 280px; }
.pf-takeaway {
margin-top: 1.4rem; padding: 0.95rem 1.2rem; background: var(--accent-soft);
border-left: 3px solid var(--accent); border-radius: 4px; font-size: 0.9rem; color: var(--ink); line-height: 1.5;
max-width: 620px; margin-left: auto; margin-right: auto;
}
.pf-takeaway b { color: var(--accent-deep); }
@media (prefers-reduced-motion: reduce) {
.infographic-profileform *, .infographic-profileform *::before, .infographic-profileform *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="pf-eyebrow">Object State</div>
<h1 class="pf-title">One Field Changes, the Other Stays</h1>
<p class="pf-sub">Type in either box — the other value never disappears.</p>
<div class="pf-grid">
<div class="pf-code-block">
<div class="pf-code-head">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3 L2 8 L6 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3 L14 8 L10 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="pf-code-name">Profile.jsx</span>
</div>
<pre><code><span class="pf-line" data-line="1"><span class="tok-kw">function</span> <span class="tok-type">Profile</span><span class="tok-punc">() {</span></span><span class="pf-line" data-line="2"> <span class="tok-kw">const</span> <span class="tok-punc">[</span>user<span class="tok-punc">,</span> setUser<span class="tok-punc">]</span> <span class="tok-punc">=</span> <span class="tok-type">useState</span><span class="tok-punc">({</span> <span class="tok-attr">name</span><span class="tok-punc">:</span> <span class="tok-str">""</span><span class="tok-punc">,</span> <span class="tok-attr">email</span><span class="tok-punc">:</span> <span class="tok-str">""</span> <span class="tok-punc">});</span></span><span class="pf-line" data-line="3"> <span class="tok-kw">return</span> <span class="tok-punc">(</span></span><span class="pf-line" data-line="4"> <span class="tok-punc"><</span><span class="tok-tag">div</span><span class="tok-punc">></span></span><span class="pf-line" data-line="5"> <span class="tok-punc"><</span><span class="tok-tag">input</span></span><span class="pf-line" data-line="6"> <span class="tok-attr">placeholder</span><span class="tok-punc">=</span><span class="tok-str">"Name"</span></span><span class="pf-line" data-line="7"> <span class="tok-attr">onChange</span><span class="tok-punc">={(</span>e<span class="tok-punc">) =></span> setUser<span class="tok-punc">({ ...</span>user<span class="tok-punc">,</span> <span class="tok-attr">name</span><span class="tok-punc">:</span> e.target.value <span class="tok-punc">})}</span></span><span class="pf-line" data-line="8"> <span class="tok-punc">/></span></span><span class="pf-line" data-line="9"> <span class="tok-punc"><</span><span class="tok-tag">input</span></span><span class="pf-line" data-line="10"> <span class="tok-attr">placeholder</span><span class="tok-punc">=</span><span class="tok-str">"Email"</span></span><span class="pf-line" data-line="11"> <span class="tok-attr">onChange</span><span class="tok-punc">={(</span>e<span class="tok-punc">) =></span> setUser<span class="tok-punc">({ ...</span>user<span class="tok-punc">,</span> <span class="tok-attr">email</span><span class="tok-punc">:</span> e.target.value <span class="tok-punc">})}</span></span><span class="pf-line" data-line="12"> <span class="tok-punc">/></span></span><span class="pf-line" data-line="13"> <span class="tok-punc"><</span><span class="tok-tag">p</span><span class="tok-punc">>{</span>user.name<span class="tok-punc">}</span> - <span class="tok-punc">{</span>user.email<span class="tok-punc">}</</span><span class="tok-tag">p</span><span class="tok-punc">></span></span><span class="pf-line" data-line="14"> <span class="tok-punc"></</span><span class="tok-tag">div</span><span class="tok-punc">></span></span><span class="pf-line" data-line="15"> <span class="tok-punc">);</span></span><span class="pf-line" data-line="16"><span class="tok-punc">}</span></span></code></pre>
</div>
<div class="pf-output-wrap">
<div class="pf-output-label">Output</div>
<div class="pf-preview">
<div class="pf-preview-bar"><span></span><span></span><span></span></div>
<div class="pf-preview-page">
<input id="pfName" type="text" placeholder="Name">
<input id="pfEmail" type="text" placeholder="Email">
<p id="pfResult"> - </p>
</div>
</div>
<div class="pf-hint" id="pfHint">Try typing in either field.</div>
</div>
</div>
<div class="pf-takeaway"><b>Each handler spreads <code style="font-family:var(--mono-font)">...user</code> first</b>, then overrides only its own field — so the other one is copied over untouched.</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('pf');
if (!root) return;
/* --- dark mode detection: body attribute > cookie > postMessage > light --- */
function applyDark(dark) {
if (dark) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
applyDark(dark);
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') applyDark(!!e.data.value);
});
/* --- live controlled-input simulation --- */
var lines = root.querySelectorAll('.pf-line');
var nameInput = root.querySelector('#pfName');
var emailInput = root.querySelector('#pfEmail');
var result = root.querySelector('#pfResult');
var hint = root.querySelector('#pfHint');
var user = { name: '', email: '' };
function renderResult() {
var n = user.name || '';
var e = user.email || '';
result.textContent = (n || '\u00a0') + ' - ' + (e || '\u00a0');
}
function highlight(fromLine, toLine) {
lines.forEach(function (el) {
var n = parseInt(el.getAttribute('data-line'), 10);
el.classList.toggle('is-current', n >= fromLine && n <= toLine);
});
}
nameInput.addEventListener('focus', function () {
highlight(5, 8);
hint.textContent = 'Typing here spreads ...user and overrides just "name".';
});
emailInput.addEventListener('focus', function () {
highlight(9, 12);
hint.textContent = 'Typing here spreads ...user and overrides just "email".';
});
[nameInput, emailInput].forEach(function (el) {
el.addEventListener('blur', function () {
highlight(-1, -1);
hint.textContent = 'Try typing in either field.';
});
});
nameInput.addEventListener('input', function () {
user = { name: nameInput.value, email: user.email };
renderResult();
});
emailInput.addEventListener('input', function () {
user = { name: user.name, email: emailInput.value };
renderResult();
});
renderResult();
})();
</script>🔹 Using Functional Update (Best Practice)
When new state depends on previous state, use a function.
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));✅ Why?
Prevents bugs
Always gets latest state
Recommended in real apps
🔹 When to Use Object State?
✅ Use object state when:
Fields are related (forms, profiles)
Updating multiple values together
❌ Avoid when:
Fields update independently
Object becomes too large → split state
❌ Common Mistakes
Mistake | Result |
|---|---|
Not using spread operator | Data loss |
Mutating object directly | No re-render |
Using stale state | Bugs |