ToolPopToolPop
React · Lesson 5 of 8

Lists, keys, forms, the boring stuff that breaks production

11 min readUpdated 24 Jun 2026

Three things trip up every React beginner: rendering lists, picking a key, and getting a form to actually accept input. All three are easy once you see what React is doing.

Lists are just .map

To render an array of things, you .map it to an array of JSX.

jsx
function RestaurantList({ items }) {
  return (
    <ul>
      {items.map(r => (
        <li key={r.id}>{r.name}</li>
      ))}
    </ul>
  );
}

That is the entire pattern. No for loop, no template directive. JavaScript you already know.

The key prop, explained

React reuses DOM nodes between renders to be fast. To do that, it needs to know which item in the new list matches which item in the old list. The key is that identity.

  • Use a stable, unique id. Database id, UUID, slug. Whatever does not change when the list reorders.
  • Keys only need to be unique among siblings, not globally.
  • Never index-as-key for dynamic lists.

Why index-as-key bites

Say you have ['Biryani', 'Dosa'] with keys 0, 1. User adds 'Idli' at the top. New list: ['Idli', 'Biryani', 'Dosa'] with keys 0, 1, 2. React thinks key 0 is the same item, so it keeps the 'Biryani' DOM node, just changes the text to 'Idli'. Any input state, focus, or animation inside that row is now on the wrong item.

The Swiggy reorder bug I spent a Saturday on: checkbox state stuck to the wrong row after delete. The "fix" was deleting key={index} and using key={item.id}.

For static lists that never reorder, index is fine. When in doubt, give every item a real id.

Controlled inputs (the React way)

In React, the input's value lives in state. You read it from state, write back via onChange. Single source of truth.

jsx
function SearchBar() {
  const [q, setQ] = useState('');
  return (
    <input
      value={q}
      onChange={e => setQ(e.target.value)}
      placeholder="Search Bengaluru"
    />
  );
}

This is why beginners think the form "swallows" input. If you write <input value={q} /> but forget onChange, React keeps forcing the value back to q, which never updates. The input looks frozen. Add onChange, life resumes.

Uncontrolled inputs

Sometimes you do not need state on every keystroke; just the value on submit. Use a ref.

jsx
const ref = useRef();
return (
  <form onSubmit={e => {
    e.preventDefault();
    console.log(ref.current.value);
  }}>
    <input ref={ref} defaultValue="" />
    <button>Go</button>
  </form>
);

Smaller bundle, less re-rendering, but you lose live validation and conditional UI. Pick controlled for anything fancy, uncontrolled for plain submit-once forms.

Quick rules

  • Always e.preventDefault() on form submit, otherwise the page reloads.
  • onChange fires on every keystroke in React, not just on blur. Yes, different from vanilla HTML.
  • Forms with many fields? Use react-hook-form. It is the answer for anything past three inputs.

Next: lifting state and Context, for when components need to talk to each other.

Free tools you can use while you learn

Chai0/1 done

Watching quietly. Tap me if you want a tip.

React Playground
preview

Try this (0 of 1 done)

  1. 1

    Add a fourth item "Filter Coffee" to the list.

    show answer
    function App() {
      const items = ['Idli', 'Vada', 'Dosa', 'Filter Coffee'];
      return <ul>{items.map((it) => (<li key={it}>{it}</li>))}</ul>;
    }