Learn ReactJS - State & Event Handling
Episode 5 of 24

Learn ReactJS - State & Event Handling

This episode covers useState for local state, the synthetic event system, controlled components for form inputs, and how React batches updates and re-renders. Everything is built on the components and JSX from the previous episode.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Static components only display data; an interesting app is one that reacts to its users. Interaction starts with two things: state, which stores changing values, and event handling, which captures user actions. Episode 5 makes you master both.

We'll cover useState for local state management, React's synthetic event system, controlled components for forms, and then how React batches updates and re-renders. This is the foundation of interactivity used in every following episode.

useState for Local State Management

Storing Values That Change

The useState hook returns a pair of values: the current value and a function to update it. The component re-renders every time the update function is called:

JSA counter with useState
import { useState } from "react"
 
function Counter() {
  const [hitung, setHitung] = useState(0)
 
  return (
    <div>
      <p>Hitungan: {hitung}</p>
      <button onClick={() => setHitung(hitung + 1)}>Tambah</button>
    </div>
  )
}

useState(0) sets the initial value 0 and returns hitung plus setHitung. Calling setHitung always replaces the value with a new one and triggers a re-render — this is the only correct way to change state.

Don't Mutate State Directly

Changing a state value directly, like hitung = hitung + 1, doesn't trigger any render. React detects changes through the update function. If the new value depends on the old one, use the callback form:

JSUpdate with a callback
const tambah = () => setHitung((sekarang) => sekarang + 1)

setHitung((sekarang) => sekarang + 1) accepts a function that receives the most recent state value. This form is required when there are several sequential updates or when the new value is computed from the old state.

Event Handling and Synthetic Events

Handling Events in JSX

Events in React are written with an on prefix and a capital letter, like onClick, onChange, and onSubmit, with the handler as the value:

JSA few event handlers
function FormSederhana() {
  const handleKlik = () => alert("Tombol diklik")
  const handleTeks = (e) => console.log("Diketik:", e.target.value)
 
  return (
    <div>
      <button onClick={handleKlik}>Klik</button>
      <input onChange={handleTeks} placeholder="Ketik di sini" />
    </div>
  )
}

onClick={handleKlik} passes a function reference, not the result of a call. Don't write onClick={handleKlik()} unless you actually want to call it during render.

The Synthetic Event System

React wraps browser events in a SyntheticEvent so their behavior is consistent across all browsers. These events have the same properties as the native events, including preventDefault and stopPropagation, but they're attached via delegation to the root DOM, which is more memory-efficient. Property names follow camelCase, for example onChange, not onchange.

Controlled Components for Form Inputs

State as the Single Source of Truth

In a controlled component, the input value is stored in state and returned through the value attribute. Every keystroke goes through onChange, which updates state, so state is always in sync with the view:

JSA controlled input
import { useState } from "react"
 
function FormNama() {
  const [nama, setNama] = useState("")
 
  const handleSubmit = (e) => {
    e.preventDefault()
    console.log("Dikirim:", nama)
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <input value={nama} onChange={(e) => setNama(e.target.value)} />
      <button type="submit">Simpan</button>
    </form>
  )
}

e.target.value reads the current text from the event, then setNama stores it in state. Because value always comes from state, the input value never diverges from what React knows — this pattern becomes the basis for validation directly in episode 11.

State Updates, Event Batching, and Re-rendering

Batching Updates Within a Single Event

React combines several setter calls in one event into a single render. That's why reading old state in the middle of a batch won't see the new value:

JSBatching updates
const handleTambah = () => {
  setHitung(hitung + 1)
  setHitung(hitung + 1)
}

The two calls above only increment hitung by one, because both read the same value from the previous render. Use the callback form so each update uses the result of the previous one:

JSCorrect sequential updates
const handleTambah = () => {
  setHitung((sekarang) => sekarang + 1)
  setHitung((sekarang) => sekarang + 1)
}

setHitung((sekarang) => sekarang + 1) runs sequentially, so the final result is hitung incremented by two. React runs the callbacks one by one within a single batched render.

Re-render Behavior

When state changes, React re-runs the component function and compares the result with the previous render via reconciliation. Only the parts that actually changed are updated in the DOM. Understanding this pattern helps you avoid wasteful renders — a topic covered more deeply in episodes 6 and 15.

Conclusion

Episode 5 gave your app a brain: useState to store and update values, synthetic events to capture interactions, controlled components for forms that always stay in sync, and an understanding of batching and re-rendering that's key to reading React's behavior.

Key takeaways:

  • useState returns a value and an update function; never mutate state directly.
  • Use the callback form when an update depends on the previous state value.
  • Event handlers are written in camelCase and receive a SyntheticEvent.
  • A controlled component keeps the input value in state via value and onChange.
  • React batches: multiple setters in one event produce a single render.
  • Re-renders are triggered by state changes, and only the changed DOM parts are updated.

In the next episode, episode 6, we'll cover effects & lifecycle with hooksuseEffect for side effects, effect cleanup and dependency arrays, useMemo and useCallback for optimization, and custom hooks for reusable logic. Your app starts reaching beyond the screen: loading data, listening to global events, and managing timers.