Recommended resources
The freely available resources this lesson will direct you to.
Welcome back. Last lesson, a button click updated a counter and called a parent-supplied callback. The handler didn’t need to inspect the click event. Text inputs and checkboxes are different: to update state from them, you need to read what changed.
In this lesson, you’ll type those event handlers and use useState to keep a small set of user-directory controls in sync with what appears on screen. Allow about 40 minutes, including the resources and a change to your Vite project.
From an event to a state update
React calls an event handler when someone interacts with an element. Your handler can read information from the event and pass a new value to a state setter. React then renders the component again with that state.
For a text input, the useful value is its text. For a checkbox, it is whether the box is checked. Both can use onChange, but they read different properties:
event.currentTarget.value // text input: string
event.currentTarget.checked // checkbox: boolean
currentTarget refers to the element whose handler is running. You will type the event so TypeScript knows that element is an HTMLInputElement.
State types are often inferred from their initial values. useState('') gives you a string state value; useState(false) gives you a boolean one. You don’t need an explicit type argument for either of those cases.
Keep inputs and state in sync
Read the React documentation’s explanation of a controlled input: its value comes from state, and its onChange handler updates that state. Then look at the corresponding checkbox example.
Read React’s input reference to see why an input needs both a state-backed value and a change handler.
In “Controlling an input with a state variable,” begin where the text explains that an initial value does not control the input’s current value. Read the controlled-input explanation through the guidance on starting with an empty string. Focus on how value and onChange work together. Then go to “Troubleshooting” → “My checkbox doesn’t update when I click on it.” Read the checkbox example through the warning about checked versus value.
The key distinction is between defaultValue and value. defaultValue supplies an initial value while the browser manages subsequent edits. A state-backed value tells React what the input should display now. If you supply value without updating it in onChange, typing will not change what the input displays.
For a checkbox, the equivalent pair is checked and onChange. Its state should start as a boolean, and the handler should read checked—not the checkbox’s string value.
Type the event when you extract a handler
When you write a handler directly inside JSX, TypeScript can infer its event type from the element:
<input onChange={(event) => console.log(event.currentTarget.value)} />
A named handler is often easier to read or reuse. Away from the JSX attribute, its parameter needs an explicit type:
import type { ChangeEvent } from 'react'
function handleQueryChange(event: ChangeEvent<HTMLInputElement>) {
setQuery(event.currentTarget.value)
}
ChangeEvent describes the kind of React event; HTMLInputElement identifies the element handling it. Notice that this is an event type, not the callback-prop signature you wrote last lesson. A component could pass such a handler as a prop, but here it will remain local.
Watch the input-change portion of React TypeScript Tutorial - 6 - Event Props from Codevolution. It shows where to place the type whether a handler is local or supplied through props.
React TypeScript Tutorial - 6 - Event Props
Watch Codevolution’s “React TypeScript Tutorial - 6 - Event Props” for the syntax of a typed input-change handler.
Watch input event typing. Look for ChangeEvent<HTMLInputElement> in the handler signature and for the point where the handler is attached to onChange. The example uses a fixed empty-string input value to concentrate on event types; in your project, connect the value to state so typing can update the display.
React’s TypeScript guide offers a useful rule of thumb: let an inline handler’s type be inferred, and annotate the event when you extract the handler into its own function.
Read React’s TypeScript guide for the reasoning behind explicit event types on named handlers.
In “Useful Types” → “DOM Events,” read the event guidance, including the named-handler example and the tip about hovering over a JSX event handler in your editor. Focus on how to find an event type when you encounter an unfamiliar control.
You do not need to memorize every event type. In your editor, hover over an element’s onChange or onClick attribute to inspect the handler type that React expects.
Build user-directory controls
Replace src/App.tsx with this component. It stores the search text and checkbox choice locally. The preset buttons also demonstrate a typed click event: one handler reads each button’s value.
import { useState, type ChangeEvent, type MouseEvent } from 'react'
function App() {
const [query, setQuery] = useState('')
const [activeOnly, setActiveOnly] = useState(false)
function handleQueryChange(event: ChangeEvent<HTMLInputElement>) {
setQuery(event.currentTarget.value)
}
function handleActiveOnlyChange(event: ChangeEvent<HTMLInputElement>) {
setActiveOnly(event.currentTarget.checked)
}
function handlePresetClick(event: MouseEvent<HTMLButtonElement>) {
setQuery(event.currentTarget.value)
}
function handleReset() {
setQuery('')
setActiveOnly(false)
}
return (
<main>
<h1>User directory controls</h1>
<label htmlFor="user-query">Search by name or email</label>
<input
id="user-query"
type="search"
value={query}
onChange={handleQueryChange}
/>
<label>
<input
type="checkbox"
checked={activeOnly}
onChange={handleActiveOnlyChange}
/>
Active accounts only
</label>
<div>
<button type="button" value="Alex" onClick={handlePresetClick}>
Try Alex
</button>
<button type="button" value="Morgan" onClick={handlePresetClick}>
Try Morgan
</button>
<button type="button" onClick={handleReset}>
Reset controls
</button>
</div>
<p>Search text: {query}</p>
<p>Active only: {activeOnly ? 'Yes' : 'No'}</p>
</main>
)
}
export default App
Trace a keystroke. React calls handleQueryChange for the input; the handler reads its current text and calls setQuery. On the next render, both the input’s value and the “Search text” paragraph use the new query. Clicking Try Alex uses a different event type, MouseEvent<HTMLButtonElement>, but updates that same state. The input reflects the preset because its displayed value comes from query, regardless of how query changed.
The reset handler needs no event information, so it takes no event parameter—just like the counter’s button handler last lesson. Type the event you use, rather than adding an event parameter to every handler.
Check the behavior and the types
Run the development server and try typing, toggling the checkbox, choosing both presets, and resetting. The displayed values should follow each interaction. Then run:
npm run typecheck
Pay particular attention to two easy mistakes. Replacing event.currentTarget.checked with event.currentTarget.value in the checkbox handler gives setActiveOnly a string where it expects a boolean; TypeScript should flag it. Removing onChange while leaving a state-backed value or checked makes the control unable to respond properly to edits.
You have not filtered any user records yet. These controls only establish reliable, typed source values for that future interface.
What to carry forward
A controlled text input pairs a state-backed value with an onChange handler that reads its text. A controlled checkbox pairs checked with a handler that reads a boolean. Inline handlers can usually rely on event-type inference; named handlers can use types such as ChangeEvent<HTMLInputElement> and MouseEvent<HTMLButtonElement>. State setters request a new render, which keeps the controls and their displayed values aligned.
Next, you’ll use state to choose between alternative interfaces—for example, what to show when data is absent, loading, or empty.