The tea shop's menu lists every tea with a star button for favourites, and a filter box above the list. It works, but every keystroke in the filter renders every row again, and starring one tea renders all of them. With hundreds of teas, typing starts to lag. The starter is that working code. Change it so a row renders only when something it shows has changed.
Keep what it does. TeaMenu takes teas, a list of { id, name, price }, and an optional onRowRender. A text box labelled Filter teas shows only the teas whose name contains the text, ignoring case. Each tea is an <li> with its name, its price as $4.00, and a Star button whose accessible name is Star plus the tea's name, like Star Sencha, with aria-pressed saying whether that tea is starred. Clicking it stars or unstars the tea. A star stays while the filter hides the tea and shows it again.
The row component calls onRowRender(tea.id) every time it renders. That line is already in the starter's TeaRow, and it's how the tests count renders, so keep it in the row.
What to fix: when the filter text changes, a row that was on screen and still is doesn't render again. Starring or unstarring a tea renders that one row once, and no other row. Rendering TeaMenu again with the same props renders no row at all. A row the filter hides and brings back is a new row, so it renders once when it comes back.
The tests render without Strict Mode, so each update means exactly one render. Export TeaMenu by name. The default export, App, renders it with sample teas and logs each row render for the Preview; the tests don't use it.
<TeaMenu teas={teas} onRowRender={(id) => console.log("rendered", id)} />
// renders, with Sencha starred:
<label>Filter teas <input value="" /></label>
<ul>
<li><span>Sencha</span> <span>$4.00</span> <button aria-label="Star Sencha" aria-pressed="true">Star</button></li>
<li><span>Assam</span> <span>$3.50</span> <button aria-label="Star Assam" aria-pressed="false">Star</button></li>
</ul>
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
];
render(<TeaMenu teas={teas} />);
const items = screen.getAllByRole("listitem");
expect(items).toHaveLength(2);
expect(within(items[0]).getByText("Sencha")).toBeInTheDocument();
expect(within(items[0]).getByText("$4.00")).toBeInTheDocument();
expect(within(items[1]).getByText("$3.50")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Star Assam" })).toHaveAttribute("aria-pressed", "false");
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
{ id: "oolong", name: "Milk Oolong", price: 6 },
{ id: "rooibos", name: "Rooibos", price: 3 },
];
render(<TeaMenu teas={teas} />);
await user.type(screen.getByLabelText("Filter teas"), "OO");
expect(screen.getAllByRole("listitem")).toHaveLength(2);
expect(screen.getByText("Milk Oolong")).toBeInTheDocument();
expect(screen.getByText("Rooibos")).toBeInTheDocument();
expect(screen.queryByText("Sencha")).not.toBeInTheDocument();
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
];
render(<TeaMenu teas={teas} />);
await user.click(screen.getByRole("button", { name: "Star Sencha" }));
expect(screen.getByRole("button", { name: "Star Sencha" })).toHaveAttribute("aria-pressed", "true");
expect(screen.getByRole("button", { name: "Star Assam" })).toHaveAttribute("aria-pressed", "false");
const filter = screen.getByLabelText("Filter teas");
await user.type(filter, "assam");
expect(screen.queryByText("Sencha")).not.toBeInTheDocument();
await user.clear(filter);
expect(screen.getByRole("button", { name: "Star Sencha" })).toHaveAttribute("aria-pressed", "true");
await user.click(screen.getByRole("button", { name: "Star Sencha" }));
expect(screen.getByRole("button", { name: "Star Sencha" })).toHaveAttribute("aria-pressed", "false");
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
{ id: "rooibos", name: "Rooibos", price: 3 },
];
const renders = {};
render(<TeaMenu teas={teas} onRowRender={(id) => (renders[id] = (renders[id] ?? 0) + 1)} />);
expect(renders).toEqual({ sencha: 1, assam: 1, rooibos: 1 });
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
{ id: "oolong", name: "Milk Oolong", price: 6 },
{ id: "rooibos", name: "Rooibos", price: 3 },
];
const renders = {};
render(<TeaMenu teas={teas} onRowRender={(id) => (renders[id] = (renders[id] ?? 0) + 1)} />);
await user.type(screen.getByLabelText("Filter teas"), "oo");
expect(screen.getAllByRole("listitem")).toHaveLength(2);
expect(renders.oolong).toBe(1);
expect(renders.rooibos).toBe(1);
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
{ id: "rooibos", name: "Rooibos", price: 3 },
];
const renders = {};
render(<TeaMenu teas={teas} onRowRender={(id) => (renders[id] = (renders[id] ?? 0) + 1)} />);
await user.click(screen.getByRole("button", { name: "Star Assam" }));
expect(renders).toEqual({ sencha: 1, assam: 2, rooibos: 1 });
await user.click(screen.getByRole("button", { name: "Star Sencha" }));
expect(renders).toEqual({ sencha: 2, assam: 2, rooibos: 1 });
await user.click(screen.getByRole("button", { name: "Star Assam" }));
expect(renders).toEqual({ sencha: 2, assam: 3, rooibos: 1 });
expect(screen.getByRole("button", { name: "Star Assam" })).toHaveAttribute("aria-pressed", "false");
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "assam", name: "Assam", price: 3.5 },
];
const renders = {};
const onRowRender = (id) => (renders[id] = (renders[id] ?? 0) + 1);
const { rerender } = render(<TeaMenu teas={teas} onRowRender={onRowRender} />);
await user.click(screen.getByRole("button", { name: "Star Sencha" }));
rerender(<TeaMenu teas={teas} onRowRender={onRowRender} />);
rerender(<TeaMenu teas={teas} onRowRender={onRowRender} />);
expect(renders).toEqual({ sencha: 2, assam: 1 });
const teas = [
{ id: "sencha", name: "Sencha", price: 4 },
{ id: "oolong", name: "Milk Oolong", price: 6 },
{ id: "rooibos", name: "Rooibos", price: 3 },
];
const renders = {};
render(<TeaMenu teas={teas} onRowRender={(id) => (renders[id] = (renders[id] ?? 0) + 1)} />);
const filter = screen.getByLabelText("Filter teas");
await user.type(filter, "oo");
await user.clear(filter);
expect(screen.getAllByRole("listitem")).toHaveLength(3);
expect(renders).toEqual({ sencha: 2, oolong: 1, rooibos: 1 });
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Rows that skip rendering. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.