React - useState (Basics)

Goal

Understand and use the useState hook to manage local component state in React.

When to use it

  • To store values that change over time within a component (e.g., input values, toggles, counters).
  • When re-rendering the UI should reflect the latest state.

Minimal pattern

const { useState } = React;
function Counter(){
  const [count, setCount] = useState(0); // 0 is the initial state
  return (
    <div>
      <p>Count: <strong>{count}</strong></p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Try it

View source
const { useState } = React;
function Counter({ step = 1 }){
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: <strong>{count}</strong></p>
      <button onClick={() => setCount(c => c + step)}>+{step}</button>
      <button onClick={() => setCount(c => c - step)} style={{marginLeft:8}}>-{step}</button>
      <button onClick={() => setCount(0)} style={{marginLeft:8}}>Reset</button>
    </div>
  );
}
const root = ReactDOM.createRoot(document.getElementById('try-usestate'));
root.render(<>
  <Counter step={1} />
  <Counter step={5} />
</>);

Syntax primer (for newcomers)

  • const [value, setValue] = useState(initial): Array destructuring returns the current state and a setter.
  • setValue(next): Triggers a re-render with new state. You can pass a value or a function.
  • setValue(v => v + 1): Functional updates use the latest state safely (recommended when next depends on previous).

Common pitfalls

  • Do not mutate state: value++; won’t re-render. Always call the setter.
  • State updates are asynchronous: read the latest value by using the functional form setValue(v => ...) when deriving from previous.
  • Each component instance has its own state; siblings do not share state unless lifted to a parent.

Exercises

  1. Build a toggle component: on/off with a single button.
  2. Make a controlled text input that mirrors its value live.
  3. Add a step prop to the counter and default it to 1.