cf.completefrontend
Open lab My progress
THE REACT FIELD GUIDE

Preserving and resetting state

React keeps state by its place in the tree. See when typed text survives a render or vanishes, why nested components reset, and how a key resets on purpose.

By the end, you can
  • 01
    Predict when state is keptRead what a component returns and say whether React keeps a child's state or throws it away after a render.
  • 02
    Avoid accidental resetsKeep wrappers and component types stable from render to render, and define every component at the top level of its file.
  • 03
    Reset state on purposeGive a component a key, like key={contact.id}, to start it fresh, and lift up state that must outlive it.

A gift message that disappears

A checkout has a note for the delivery driver and, when the order is a gift, a gift message. Type something in both boxes in the result. Then untick This is a gift, and tick it again:

A checkout with two notesPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} export default function App() {  const [isGift, setIsGift] = useState(true);  return (    <>      <NoteBox label="Note for the driver" />      {isGift && <NoteBox label="Gift message" />}      <label>        <input          type="checkbox"          checked={isGift}          onChange={(event) => setIsGift(event.target.checked)}        />        This is a gift      </label>    </>  );}
ResultLoading…

NoteBox (lines 3 to 13) keeps its own text on line 4, and line 9 is a controlled input that shows it. App renders the driver's NoteBox on line 19 every time, and the gift NoteBox on line 20 only while isGift is true. Lines 21 to 28 are the checkbox that sets isGift.

The driver's note survives. The gift message comes back empty. Both boxes are the same component, and App rendered again both times. The difference is that the gift box left the page for a moment, and the driver's box never did. React decides whether to keep a component's state by where the component is in the render tree, and whether it stays there.

Two trees of the checkout. Left, gift ticked: App with two NoteBox children, the first holding Ring twice and the second Love you. Right, gift unticked: App with the first NoteBox, still holding Ring twice, and a faded, crossed-out second NoteBox: its text is gone.gift tickedAppNoteBoxNoteBox1st child2nd child"Ring twice""Love you"gift untickedAppNoteBoxNoteBox1st childremoved"Ring twice"text gone
React keeps each NoteBox's text at its place under App: first child, second child. Untick the box, and the second place is empty, so React throws its text away. Tick it again, and a new NoteBox starts there with no text.
Real-life analogyPapers left on a classroom desk

In a classroom, papers belong to a desk, not to a student. Whoever sits at the second desk in the first row finds what was left there. If the desk is carried out of the room, its papers go with it, and the desk that comes back later is empty. A name card on a desk marks it for one student; change the card, and the desk is cleared for the next one.

In real life: A desk: row 1, second seat
In React: A place in the render tree: App's second child
In real life: The papers left on that desk
In React: The state React keeps for that place
In real life: A new student sits there and finds the papers
In React: Same component, same place, new props: state kept
In real life: The desk is carried out; a new one arrives empty
In React: The component leaves the tree and comes back: state reset
In real life: A name card on the desk changes, so the desk is cleared
In React: A new key: state reset
The main idea

React keeps a component's state for as long as the same component stays in the same place in the tree. If it's removed, or a different component takes its place, React throws the state away. A key lets you decide what counts as "the same".

State belongs to a place in the tree

It looks as if text lives inside NoteBox. It doesn't. NoteBox is a function, and its local variables start over each time React calls it. When it calls useState(""), it asks React for the value React is keeping for it. React keeps that value with the component's place in the render tree.

A place is the path from the top: "App's first child", "App's second child". In the checkout, the driver's note is App's first child, and the gift message is its second. That's why two NoteBoxes on one page each have their own text, as the State lesson showed: they're two places.

Each time a component renders, React compares the new tree with the last one, place by place:

  • Same component in the same place: React keeps its state and gives it the new props.
  • Nothing in a place that had a component: React removes the component and throws its state away.
  • A component in a place that was empty: React makes a new one, with fresh state from useState's first value.
  • A different component in a place: React removes the old one, state and all, and makes the new one.

Line 20, {isGift && <NoteBox label="Gift message" />}, gives the second place either a NoteBox or false, which renders nothing. When you untick the box, the second place is empty, so React removes the gift NoteBox and its text. When you tick it, the place gets a NoteBox again: a new one, starting at "". The driver's note never left its place, so its text stays.

Inside React, each place in the tree is an object called a fiber, and your state is stored on it. When React removes the fiber, the state goes with it. You don't need those details to use state; the Fiber and Reconciliation lessons, at the end of the course, show the code that does this comparison.

Same component, same place: React keeps the state

Here is a box that switches between a gift message and a note for the driver. Type Happy birthday in it, then press Switch note:

One NoteBox, two labelsPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} export default function App() {  const [isGift, setIsGift] = useState(true);  return (    <>      {isGift ? (        <NoteBox label="Gift message" />      ) : (        <NoteBox label="Note for the driver" />      )}      <button onClick={() => setIsGift(!isGift)}>Switch note</button>    </>  );}
ResultLoading…

Lines 19 to 23 pick between two NoteBox tags with a ternary, ?:. Line 20 is the gift message and line 22 the driver's note. Line 24 flips isGift.

The label changes to Note for the driver, but your Happy birthday stays in the box. You wrote two tags, yet both put a NoteBox in the same place: App's first child. React never sees your ?:; it only sees the tree you return. Before the click, that tree had a NoteBox first; after, it still does. Same component, same place, so React keeps the state and only passes a new label.

The same happens with two returns in an if. Try it:

The same choice with two returnsPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} export default function App() {  const [isGift, setIsGift] = useState(true);  if (isGift) {    return (      <>        <NoteBox label="Gift message" />        <button onClick={() => setIsGift(false)}>Switch note</button>      </>    );  }  return (    <>      <NoteBox label="Note for the driver" />      <button onClick={() => setIsGift(true)}>Switch note</button>    </>  );}
ResultLoading…

Line 17 decides which of two returns runs. Line 20 returns a gift NoteBox; line 27 returns a driver's one. Both are App's first child, so your text survives Switch note here too.

Two halves. Left, same component: a NoteBox holding Hi becomes a NoteBox with a new label, still holding Hi: kept. Right, different component: a NoteBox holding Hi becomes a LongNote holding nothing: reset.same componentNoteBoxNoteBoxGift messageNote for the driver"Hi""Hi"keptdifferent componentNoteBoxLongNote"Hi"""reset
Left: after Switch note, the same place still has a NoteBox, so React keeps its text and only changes the label. Right: a LongNote takes the place of the NoteBox, so React removes the NoteBox and its text, and LongNote starts empty.

Here, keeping the text is a bug: a birthday message shouldn't turn into instructions for the driver. You'll fix it with a key in a later section. Often, though, keeping state is what you want: a counter that changes color, or a form that gets a new title, shouldn't forget what you typed.

A different component or parent resets it

Now the shop adds a bigger box for long messages. Type a message, then press Longer message, and then Short message:

Swapping NoteBox for LongNotePop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} function LongNote() {  const [text, setText] = useState("");  return (    <p>      <textarea        aria-label="Long gift message"        value={text}        onChange={(event) => setText(event.target.value)}      />    </p>  );} export default function App() {  const [isLong, setIsLong] = useState(false);  return (    <>      {isLong ? <LongNote /> : <NoteBox label="Gift message" />}      <button onClick={() => setIsLong(!isLong)}>        {isLong ? "Short message" : "Longer message"}      </button>    </>  );}
ResultLoading…

LongNote (lines 15 to 26) keeps its own text too, in a <textarea>. Line 32 puts a LongNote or a NoteBox in App's first place. Your text is gone after the first press, and gone again after the second. A LongNote isn't a NoteBox, even with the same kind of state, so React removes one and makes the other from scratch.

A different element above a component does the same, for everything inside it. This shop highlights the box by wrapping it in a div with a class. Type a message, then press Highlight:

The same NoteBox in a different wrapperPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} export default function App() {  const [isHighlighted, setIsHighlighted] = useState(false);  return (    <>      {isHighlighted ? (        <div className="highlight">          <NoteBox label="Gift message" />        </div>      ) : (        <section>          <NoteBox label="Gift message" />        </section>      )}      <button onClick={() => setIsHighlighted(!isHighlighted)}>Highlight</button>    </>  );}
ResultLoading…

Lines 21 and 25 are the same NoteBox, with the same label. But line 20 wraps it in a div, and line 24 in a section. When the section becomes a div, React removes the section and its whole branch of the tree, and the NoteBox inside goes with it. The text vanishes.

Two trees. Left, before Highlight: App, then a section, then a NoteBox holding Hi. Right, after Highlight: App, then a div, then a new NoteBox holding nothing, beside a faded, crossed-out section and NoteBox: the old branch, removed.before HighlightAppsectionNoteBox"Hi"after HighlightAppdivNoteBox""sectionNoteBoxremoved
Pressing Highlight puts a div where the section was. A different element type in that place means React removes the section and everything under it, including the NoteBox and its text. The NoteBox under the new div starts empty.

The rule of thumb: to keep state, keep the tree's shape the same from one render to the next. Here, use one div and change only its class: <div className={isHighlighted ? "highlight" : ""}>. An attribute change keeps the element, so the text stays. Exercise 3 asks you to make that change.

Try every kind of change in the playground. It runs the note box with React on this page, in the structure the code shows. Type in the box, press Switch, and see whether your text is still there:

Playground: kept or reset?
What Switch changesJSX
{isOn ? (
  <NoteBox label="Note for the driver" />
) : (
  <NoteBox label="Gift message" />
)}
The page0 switches

Tree now: App › NoteBox

Try it yourself
Type, then press Switch.

Type a few words in the box, then press Switch. Watch whether your text is still there.

The note box runs here, with React on this page, in the structure the code shows. Pick a change, type in the box, and press Switch. Picking a change starts again. For Defined inside App, the playground makes a new NoteBox function on each Switch, as a NoteBox written inside App would be made on each render.

Only New label keeps your text. Different component, Different wrapper, Different key, and Defined inside App all start empty. How to read the table: each row is one thing that can change between two renders, whether the NoteBox keeps its text, and why.

What keeps state, and what resets it
What changes between rendersDoes the text stay?Why
Its props, like labelKept.The same component in the same place: React gives it the new props and keeps its state.
Which branch of a ?: or an if returns itKept, when both branches put the same component in the same place.React sees only the tree you return, not which line of your code made it.
The component, NoteBox to LongNoteReset.A different component in that place: React removes the old one and its state.
An element above it, section to divReset, with everything inside.React removes the whole branch under the element that changed.
Its keyReset.A new key tells React it's a different component.
A className on the element above itKept.The element is still a div; only one of its attributes changed.

Never define a component inside another

The Your first component lesson warned you not to write a component inside another. Here is what goes wrong. Type a review, then press Helpful:

ReviewBox written inside App (a bug on purpose)Pop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; export default function App() {  const [helpful, setHelpful] = useState(0);   function ReviewBox() {    const [text, setText] = useState("");    return (      <input        aria-label="Your review"        value={text}        onChange={(event) => setText(event.target.value)}      />    );  }   return (    <>      <ReviewBox />      <button onClick={() => setHelpful(helpful + 1)}>Helpful ({helpful})</button>    </>  );}
ResultLoading…

Your review vanishes. ReviewBox (lines 6 to 15) is written inside App, so line 6 runs every time App runs, and each time it makes a new function. Pressing Helpful sets helpful on line 20, so App renders again and makes a new ReviewBox. On line 19, React finds a component in the first place that is a different function from last time, so to React it's a different component. It removes the old one, with your text, and makes a new one.

The fix is to move the function out, to the top level of the file:

ReviewBox at the top levelPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; function ReviewBox() {  const [text, setText] = useState("");  return (    <input      aria-label="Your review"      value={text}      onChange={(event) => setText(event.target.value)}    />  );} export default function App() {  const [helpful, setHelpful] = useState(0);  return (    <>      <ReviewBox />      <button onClick={() => setHelpful(helpful + 1)}>Helpful ({helpful})</button>    </>  );}
ResultLoading…

Now ReviewBox (lines 3 to 12) is made once, when the file loads. Every render of App puts the same function on line 18, so React keeps its state. Type, press Helpful, and your review stays. If an inner component needs a value from App, pass it as a prop instead of reaching for App's variables.

Why it's worse than it looks

If the review's text lived in App, as a lifted input's does, each key would render App and make a new ReviewBox. The box would lose its focus after every letter, so you could type only one letter at a time. React also throws away and rebuilds the box's DOM each time, which is slow.

Resetting state on purpose with a key

Sometimes React keeping state is the bug. In this support chat, type a message to Asha, then click Ben:

A chat without a keyPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; const contacts = [  { id: "asha", name: "Asha" },  { id: "ben", name: "Ben" },  { id: "chen", name: "Chen" },]; function ChatWindow({ contact }) {  const [draft, setDraft] = useState("");  return (    <section>      <h3>Chat with {contact.name}</h3>      <input        aria-label="Message"        placeholder={"Message " + contact.name}        value={draft}        onChange={(event) => setDraft(event.target.value)}      />    </section>  );} export default function App() {  const [contact, setContact] = useState(contacts[0]);  return (    <>      {contacts.map((item) => (        <button key={item.id} onClick={() => setContact(item)}>          {item.name}        </button>      ))}      <ChatWindow contact={contact} />    </>  );}
ResultLoading…

The heading says Chat with Ben, but your message to Asha is still in the box, one click from going to the wrong person. ChatWindow (lines 9 to 22) keeps draft on line 10. Line 33 always renders one ChatWindow in the same place; only its contact prop changes. Same component, same place: React keeps the draft. (The UI as a tree lesson met this chat, too.)

You want each person's chat to be a different component to React. A key says that. You used keys in Rendering lists to tell list items apart, and they work on any component: React keeps state only while both the type and the key stay the same.

A chat with key={contact.id}Pop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; const contacts = [  { id: "asha", name: "Asha" },  { id: "ben", name: "Ben" },  { id: "chen", name: "Chen" },]; function ChatWindow({ contact }) {  const [draft, setDraft] = useState("");  return (    <section>      <h3>Chat with {contact.name}</h3>      <input        aria-label="Message"        placeholder={"Message " + contact.name}        value={draft}        onChange={(event) => setDraft(event.target.value)}      />    </section>  );} export default function App() {  const [contact, setContact] = useState(contacts[0]);  return (    <>      {contacts.map((item) => (        <button key={item.id} onClick={() => setContact(item)}>          {item.name}        </button>      ))}      <ChatWindow key={contact.id} contact={contact} />    </>  );}
ResultLoading…

Line 33 is the only change: <ChatWindow key={contact.id} contact={contact} />. When you click Ben, the key changes from "asha" to "ben". React removes Asha's ChatWindow, with her draft, and makes a new one for Ben, with an empty box. Click back to Asha: her draft is gone too, because her window was removed. The practical section keeps it.

Two halves, after switching from Asha to Ben. Left, no key: App, then one ChatWindow showing Ben, whose draft still says See you at 5. Right, key equals contact id: App, then a ChatWindow with key ben and an empty draft, beside a faded, crossed-out ChatWindow with key asha that was removed.no keyAppChatWindowcontact: Bendraft: "See you at 5"Asha's draftkey={contact.id}AppChatWindowkey="ben"draft: ""ChatWindowkey="asha"removed
Left: without a key, Ben's chat is the same ChatWindow in the same place, so Asha's draft is still in it. Right: with key={contact.id}, the key changes from asha to ben. React removes Asha's window and its draft, and makes a new one for Ben.

A key isn't global. React compares keys only among the children of one parent, so "asha" can be a key in a contacts list and in a chat list at once. The key decides which child of this parent a component is.

There is a second way to reset: render the choices in different places. It suits a small, fixed number of choices:

Two places, one for each personJSX
{isAsha && <ChatWindow contact={contacts[0]} />}
{!isAsha && <ChatWindow contact={contacts[1]} />}

Line 1 is App's first place and line 2 its second. Asha's window always lives in the first, and Ben's in the second, so switching removes one and makes the other. It's a fragment of an app, so it doesn't run on its own. How to read the table: each row is a way to reset, what it looks like, and when to use it.

Two ways to reset state on purpose
Way to resetWhat it looks likeWhen to use it
Render it in two different places{isAsha && <ChatWindow … />} and {!isAsha && <ChatWindow … />}A few fixed choices, like two people.
Give it a key<ChatWindow key={contact.id} … />Any number of choices, like contacts from a list.

A key is also the easy way to clear a whole form. Keep a number in the parent, pass it as the form's key, and add one to it: the form, and every box in it, starts over. Exercise 5 does that.

A chat that keeps a draft for each contact

The key gave each person a fresh box, but a real chat app remembers what you half-wrote to each person. React throws state away when a component leaves the tree, so the drafts must live in a component that stays: App. Lift them up. Type to Asha, click Ben, then click Asha (draft):

App keeps a draft for each contactPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; const contacts = [  { id: "asha", name: "Asha" },  { id: "ben", name: "Ben" },  { id: "chen", name: "Chen" },]; function ChatWindow({ contact, draft, onDraftChange, onSend }) {  return (    <section>      <h3>Chat with {contact.name}</h3>      <input        aria-label="Message"        value={draft}        onChange={(event) => onDraftChange(event.target.value)}      />      <button onClick={onSend}>Send</button>    </section>  );} export default function App() {  const [contact, setContact] = useState(contacts[0]);  const [drafts, setDrafts] = useState({});  const draft = drafts[contact.id] ?? "";   function changeDraft(text) {    setDrafts({ ...drafts, [contact.id]: text });  }   return (    <>      {contacts.map((item) => (        <button key={item.id} onClick={() => setContact(item)}>          {item.name}          {drafts[item.id] ? " (draft)" : ""}        </button>      ))}      <ChatWindow        contact={contact}        draft={draft}        onDraftChange={changeDraft}        onSend={() => changeDraft("")}      />    </>  );}
ResultLoading…

Line 25 keeps drafts, an object with one entry per contact id, like { asha: "Hi Asha" }. Line 26 reads the open contact's draft, or "" when there is none. changeDraft (lines 28 to 30) makes a new object with spread and replaces that one contact's entry. ChatWindow has no state now: line 15 shows the draft it's given, and line 16 reports each key. Line 37 marks contacts with a draft, and Send on line 18 clears it.

Switching contacts no longer loses anything, because the drafts never lived in ChatWindow. How to read the table: each row is a way to keep state when a component leaves the screen, how it works, and when it fits.

Ways to keep state when a component leaves
Way to keep itHowWhen it fits
Keep it in the tree and hide it with CSSRender every chat, and hide all but one with hidden.A few small parts. Many hidden parts slow the page down.
Lift the state upApp keeps a draft for each contact, by its id.Most cases: the parent remembers, whichever chat is open.
Save it outside ReactWrite each draft to localStorage, and read it back when the chat opens.Drafts that should survive closing the tab.
<Activity mode="hidden">Hide part of the page while React keeps its state (added in React 19.2).Tabs and pages the reader comes back to. The Activity lesson covers it.

The last row is new: <Activity> came out in React 19.2. Its hidden mode hides a part of the page and keeps its state, so a tab you come back to still has what you typed. The Activity lesson covers it.

Kept or reset?
  • <NoteBox label="Gift" /> becomes <NoteBox label="Driver" />
  • {a ? <NoteBox /> : <NoteBox />} when a changes
  • <NoteBox /> becomes <LongNote /> in the same place
  • <div><NoteBox /></div> becomes <section><NoteBox /></section>
  • {show && <NoteBox />} when show goes false, then true
  • <NoteBox key={id} /> when id changes
  • <div className={a ? "on" : ""}><NoteBox /></div> when a changes
  • NoteBox is written inside App, and App renders again
Try it yourself
0 of 8 correct

Sort each change: after it, does the NoteBox keep its text, or start empty?

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "State lives inside the component function." No. React keeps it, tied to the component's place in the tree. The function asks for it with useState on every render.
  • "A new prop resets a component's state." No. New props reach the same component in the same place, and its state stays. That's why the draft followed you to Ben.
  • "Two different JSX tags are two different components." No. If both put the same component in the same place, React sees one component that stayed.
  • "Hiding a component with && keeps its state for later." No. false removes it from the tree, and its state goes with it.
  • "Only the component that changed loses its state." No. When an element changes type, React removes everything under it, including components that didn't change.
  • "Keys are only for lists." No. Any component can take a key, and a new key resets it.
  • "Keys must be unique across the whole app." No. They only need to be unique among the children of one parent.

Practice exercises

For each one, draw the tree before and after the click. Ask: is the same component in the same place, with the same key? Press Edit & run to check.

Exercise 1 · Warm-upA banner above the note

Predict first: you type Hi in the box, then press Show banner. The banner appears above the box. What is in the box?

import { useState } from "react";

function NoteBox({ label }) {
  const [text, setText] = useState("");
  return (
    <p>
      <label>
        {label}{" "}
        <input value={text} onChange={(event) => setText(event.target.value)} />
      </label>
    </p>
  );
}

function Banner() {
  return <p>Free delivery on orders over $20!</p>;
}

export default function App() {
  const [showBanner, setShowBanner] = useState(false);
  return (
    <>
      {showBanner && <Banner />}
      <NoteBox label="Gift message" />
      <button onClick={() => setShowBanner(!showBanner)}>Show banner</button>
    </>
  );
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upTea or water

    Predict first: you press the tea button 3 times, then Switch drink twice. What does the tea button say?

    import { useState } from "react";
    
    function CupCounter() {
      const [cups, setCups] = useState(0);
      return <button onClick={() => setCups(cups + 1)}>Cups of tea: {cups}</button>;
    }
    
    function GlassCounter() {
      const [glasses, setGlasses] = useState(0);
      return <button onClick={() => setGlasses(glasses + 1)}>Glasses of water: {glasses}</button>;
    }
    
    export default function App() {
      const [showTea, setShowTea] = useState(true);
      return (
        <>
          {showTea ? <CupCounter /> : <GlassCounter />}
          <button onClick={() => setShowTea(!showTea)}>Switch drink</button>
        </>
      );
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeHighlight without wiping

      Highlight wipes the gift message. Fix it. After your fix, type Hi and press Highlight. What is in the box?

      import { useState } from "react"; function NoteBox({ label }) {  const [text, setText] = useState("");  return (    <p>      <label>        {label}{" "}        <input value={text} onChange={(event) => setText(event.target.value)} />      </label>    </p>  );} export default function App() {  const [isHighlighted, setIsHighlighted] = useState(false);  return (    <>      {isHighlighted ? (        <div className="highlight">          <NoteBox label="Gift message" />        </div>      ) : (        <section>          <NoteBox label="Gift message" />        </section>      )}      <button onClick={() => setIsHighlighted(!isHighlighted)}>Highlight</button>    </>  );}
      ResultLoading…

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeA review that survives Helpful

        Clicking Helpful wipes the review. Fix it. After your fix, type Great tea and press Helpful. What is in the box?

        import { useState } from "react"; export default function App() {  const [helpful, setHelpful] = useState(0);   function ReviewBox() {    const [text, setText] = useState("");    return (      <input        aria-label="Your review"        value={text}        onChange={(event) => setText(event.target.value)}      />    );  }   return (    <>      <ReviewBox />      <button onClick={() => setHelpful(helpful + 1)}>Helpful ({helpful})</button>    </>  );}
        ResultLoading…

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeStart over

          Start over should empty the whole form, but nothing happens. Fix it with one change, without touching GiftForm. After your fix, fill in both boxes and press Start over. What does the Card line say?

          import { useState } from "react"; function GiftForm() {  const [to, setTo] = useState("");  const [message, setMessage] = useState("");  return (    <>      <input aria-label="To" value={to} onChange={(event) => setTo(event.target.value)} />      <input aria-label="Message" value={message} onChange={(event) => setMessage(event.target.value)} />      <p>Card: {to || message ? to + ": " + message : "(empty)"}</p>    </>  );} export default function App() {  const [round, setRound] = useState(0);  return (    <>      <GiftForm />      <button onClick={() => setRound(round + 1)}>Start over</button>    </>  );}
          ResultLoading…

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeKeep every draft

            Each chat starts empty, but now a half-written message is lost when you switch away. Keep a draft for each contact. After your fix, type Hi Asha, click Ben, then Asha. What is in the box?

            import { useState } from "react"; const contacts = [  { id: "asha", name: "Asha" },  { id: "ben", name: "Ben" },  { id: "chen", name: "Chen" },]; function ChatWindow({ contact }) {  const [draft, setDraft] = useState("");  return (    <section>      <h3>Chat with {contact.name}</h3>      <input        aria-label="Message"        placeholder={"Message " + contact.name}        value={draft}        onChange={(event) => setDraft(event.target.value)}      />    </section>  );} export default function App() {  const [contact, setContact] = useState(contacts[0]);  return (    <>      {contacts.map((item) => (        <button key={item.id} onClick={() => setContact(item)}>          {item.name}        </button>      ))}      <ChatWindow key={contact.id} contact={contact} />    </>  );}
            ResultLoading…

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, write the tree before and after, place by place, with each component's type and key.

              The Preserving and resetting state quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Where does React keep a component's state?

                Choose an answer to see the explanation.

              2. Question 2 of 8You type Hi in the Gift box, then click Switch. What is in the box?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { useState } from "react";
                
                function NoteBox({ label }) {
                  const [text, setText] = useState("");
                  return (
                    <p>
                      <label>
                        {label}{" "}
                        <input value={text} onChange={(event) => setText(event.target.value)} />
                      </label>
                    </p>
                  );
                }
                
                export default function App() {
                  const [isOn, setIsOn] = useState(false);
                  return (
                    <>
                      {isOn ? <NoteBox label="Driver" /> : <NoteBox label="Gift" />}
                      <button onClick={() => setIsOn(!isOn)}>Switch</button>
                    </>
                  );
                }

                Choose an answer to see the explanation.

              3. Question 3 of 8This time the element around the NoteBox changes. You type Hi, then click Switch. What is in the box?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { useState } from "react";
                
                function NoteBox({ label }) {
                  const [text, setText] = useState("");
                  return (
                    <p>
                      <label>
                        {label}{" "}
                        <input value={text} onChange={(event) => setText(event.target.value)} />
                      </label>
                    </p>
                  );
                }
                
                export default function App() {
                  const [isOn, setIsOn] = useState(false);
                  return (
                    <>
                      {isOn ? <div><NoteBox label="Gift" /></div> : <article><NoteBox label="Gift" /></article>}
                      <button onClick={() => setIsOn(!isOn)}>Switch</button>
                    </>
                  );
                }

                Choose an answer to see the explanation.

              4. Question 4 of 8Here only the key changes. You type Hi, then click Switch. What is in the box?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { useState } from "react";
                
                function NoteBox({ label }) {
                  const [text, setText] = useState("");
                  return (
                    <p>
                      <label>
                        {label}{" "}
                        <input value={text} onChange={(event) => setText(event.target.value)} />
                      </label>
                    </p>
                  );
                }
                
                export default function App() {
                  const [isOn, setIsOn] = useState(false);
                  return (
                    <>
                      <NoteBox key={isOn ? "on" : "off"} label="Gift" />
                      <button onClick={() => setIsOn(!isOn)}>Switch</button>
                    </>
                  );
                }

                Choose an answer to see the explanation.

              5. Question 5 of 8Why does a component written inside another component lose its state whenever the outer one renders?

                Choose an answer to see the explanation.

              6. Question 6 of 8Two different lists each render a <ChatWindow key="asha" />. Is that a problem?

                Choose an answer to see the explanation.

              7. Question 7 of 8Each contact's chat should start empty when you switch. What's the simplest change?

                Choose an answer to see the explanation.

              8. Question 8 of 8What happens to a NoteBox's text when {show && <NoteBox />} gets a show of false?

                Choose an answer to see the explanation.

              Key takeaways

              • React keeps state, not your component function. It ties the state to the component's place in the render tree.
              • The same component in the same place keeps its state, whatever props it gets and whichever line of your code made it.
              • Removing a component, or putting a different one in its place, throws its state away. A changed parent element takes its whole branch with it.
              • Define components at the top level of a file. One written inside another is a new type on every render, so it resets.
              • A new key resets a component on purpose. Keys only need to be unique among siblings.
              • To keep state when a component leaves, lift it up, hide the component instead of removing it, or save it outside React.

              Remember the one-liner.
              Same type, same place, same key: React keeps the state. Change any of them, and it starts fresh.

              Coming next: Extracting state logic into a reducer. When a component updates its state in many ways, useReducer gathers every update into one function you can read and test.

              CompleteFrontend Clear concepts. Working examples.