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 whateversearchcurrently is.onChangefires on every keystroke.event.target.valueis the box's new text, andsetSearch(...)feeds it straight back into state.- That round trip, state sets
value, user types,onChangefires,setSearchupdates state, state setsvalueagain, 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
hasWatchedback in State, just flipping a boolean.!showWatchedOnlyis the opposite of whatever it currently is, andsetShowWatchedOnlyasks 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():showWatchedOnlyoff → every movie passes (return true). On → only movies withwatched: truepass. - 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
moviesitself,setMoviesis never called here. Filtering only decides what gets rendered this render, the underlying state list is untouched.