all topics

Filtering

Controlled inputs: value + onChange

An input whose value React controls, not the DOM.

jsx
<input
    type="text"
    placeholder="Search movies..."
    value={search}
    onChange={event => setSearch(event.target.value)}
/>
  • value={search} ties what's shown in the box directly to state, the input always displays whatever search currently is.
  • onChange fires on every keystroke. event.target.value is the box's new text, and setSearch(...) feeds it straight back into state.
  • That round trip, state sets value, user types, onChange fires, setSearch updates state, state sets value again, is what "controlled" means: the DOM never holds a value React doesn't already know about.

A toggle button

jsx
<button onClick={() => setShowWatchedOnly(!showWatchedOnly)}>
    {showWatchedOnly ? "Show All" : "Show Watched"}
</button>
  • Same idea as hasWatched back in State, just flipping a boolean. !showWatchedOnly is the opposite of whatever it currently is, and setShowWatchedOnly asks React to re-render with that flipped value.
  • The button's own label reads off the same state, so it always describes what clicking it would do next, not what's currently showing.

Multiple pieces of state, side by side

jsx
const [movies, setMovies] = useState([...])
const [showWatchedOnly, setShowWatchedOnly] = useState(false)
const [search, setSearch] = useState("")

One component can own as many separate pieces of state as it needs. useState doesn't need to be called once, each call is its own independent value with its own setter, they don't interfere with each other.

Chaining .filter() before .map()

jsx
movies
    .filter(movie => {
        if (showWatchedOnly) {
            return movie.watched
        }
        return true
    })
    .filter(movie =>
        movie.title.toLowerCase().includes(search.toLowerCase())
    )
    .map(movie => (
        <MovieCard key={movie.id} movie={movie} onWatched={handleWatched} />
    ))
  • .filter() returns a new array holding only the items that pass. Chaining two of them just applies two conditions in sequence, whatever survives both goes on to .map().
  • First .filter(): showWatchedOnly off → every movie passes (return true). On → only movies with watched: true pass.
  • Second .filter(): .toLowerCase() on both sides makes the match case-insensitive. .includes() matches the search text anywhere in the title, not just at the start.
  • None of this touches movies itself, setMovies is never called here. Filtering only decides what gets rendered this render, the underlying state list is untouched.