import { useState } from 'react';
const SONGS = [
{ id: 1, title: 'Blue Hour', artist: 'Mira', liked: true },
{ id: 2, title: 'Paper Boats', artist: 'Tom', liked: false },
{ id: 3, title: 'Night Bus', artist: 'Mira', liked: true },
{ id: 4, title: 'Salt', artist: 'Jun', liked: false }
];
// Steps 3 and 4: the minimal state is the search text and the checkbox.
// Both are used by SearchBar and by SongList, so they live in their parent.
export default function FilterablePlaylist() {
const [query, setQuery] = useState('');
const [likedOnly, setLikedOnly] = useState(false);
// Everything else is computed on demand, not stored.
const shown = SONGS.filter(
song =>
song.title.toLowerCase().includes(query.toLowerCase()) &&
(!likedOnly || song.liked)
);
return (
<div>
<SearchBar
query={query}
likedOnly={likedOnly}
onQueryChange={setQuery}
onLikedOnlyChange={setLikedOnly}
/>
<SongList songs={shown} />
</div>
);
}
// Step 5: inverse data flow. The setters come down as props.
function SearchBar({ query, likedOnly, onQueryChange, onLikedOnlyChange }) {
return (
<form>
<input
aria-label="Search songs"
value={query}
onChange={e => onQueryChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={likedOnly}
onChange={e => onLikedOnlyChange(e.target.checked)}
/>
Only liked songs
</label>
</form>
);
}
// Step 2: the static parts only read their props.
function SongList({ songs }) {
return (
<ul>
{songs.map(song => <SongRow key={song.id} song={song} />)}
</ul>
);
}
function SongRow({ song }) {
return <li>{song.title}</li>;
}
// A run only mounts. Act like a user, one step every 50 ms.
const search = value => {
const input = document.querySelector('input');
const win = input.ownerDocument.defaultView;
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), 'value').set.call(input, value);
input.dispatchEvent(new win.Event('input', { bubbles: true }));
};
const print = () =>
console.log([...document.querySelectorAll('li')].map(li => li.textContent).join(', '));
const plan = [
print,
() => search('o'),
print,
() => document.querySelector('input[type=checkbox]').click(),
print,
() => search(''),
print
];
plan.forEach((step, i) => setTimeout(step, 50 * (i + 1)));