The tea shop's dashboard has a search box, a Larger text button, and a sales chart that's slow to render. Every keystroke in the search box renders the whole page, chart included, and so does the button. The starter is that page. Restructure it so the chart doesn't render again when you type or change the text size.
Try to do it by changing where state lives, without memo, useMemo, or useCallback: the search text only matters to the search, and the text size wraps the chart but doesn't change what the chart shows. The tests only count chart renders, so memo would pass them too. The point of this exercise is the restructure.
Keep what the page does. Dashboard takes teas, a list of tea names, and an optional onChartRender, which it passes to SalesChart as onRender. It has a text box labelled Search teas. While the search has text (ignoring spaces at either end), the page shows how many teas contain it, ignoring case, as 2 matches, 1 match, or 0 matches, and a list labelled Suggestions with their names. With an empty search, neither shows.
The Larger text button has aria-pressed. The page's <main> has data-size="large" while it's pressed and data-size="normal" otherwise, and it wraps everything: the button, the search, and the chart. Changing the text size keeps the search text.
SalesChart stays as it is: it calls onRender every time it renders, which is how the tests count renders. What to fix: typing in the search box, or pressing Larger text, never renders SalesChart again. The tests render without Strict Mode. Export Dashboard and SalesChart by name; the default export, App, renders the dashboard and logs each chart render for the Preview.
<Dashboard teas={["Sencha", "Genmaicha", "Assam"]} onChartRender={() => console.log("chart")} />
// renders, after typing "cha":
<main data-size="normal">
<button aria-pressed="false">Larger text</button>
<label>Search teas <input value="cha" /></label>
<p>2 matches</p>
<ul aria-label="Suggestions"><li>Sencha</li><li>Genmaicha</li></ul>
<figure>...the chart...</figure>
</main>
render(<Dashboard teas={["Sencha", "Assam", "Genmaicha"]} />);
expect(screen.queryByRole("list", { name: "Suggestions" })).not.toBeInTheDocument();
await user.type(screen.getByLabelText("Search teas"), "CHA");
expect(screen.getByText("2 matches")).toBeInTheDocument();
const suggestions = within(screen.getByRole("list", { name: "Suggestions" })).getAllByRole("listitem");
expect(suggestions.map((item) => item.textContent)).toEqual(["Sencha", "Genmaicha"]);
render(<Dashboard teas={["Sencha", "Assam", "Genmaicha"]} />);
const search = screen.getByLabelText("Search teas");
await user.type(search, "assam");
expect(screen.getByText("1 match")).toBeInTheDocument();
await user.type(search, "x");
expect(screen.getByText("0 matches")).toBeInTheDocument();
await user.clear(search);
await user.type(search, " ");
expect(screen.queryByText(/match/)).not.toBeInTheDocument();
expect(screen.queryByRole("list", { name: "Suggestions" })).not.toBeInTheDocument();
render(<Dashboard />);
const button = screen.getByRole("button", { name: "Larger text" });
expect(button).toHaveAttribute("aria-pressed", "false");
expect(screen.getByRole("main")).toHaveAttribute("data-size", "normal");
await user.click(button);
expect(screen.getByRole("button", { name: "Larger text" })).toHaveAttribute("aria-pressed", "true");
expect(screen.getByRole("main")).toHaveAttribute("data-size", "large");
await user.click(screen.getByRole("button", { name: "Larger text" }));
expect(screen.getByRole("main")).toHaveAttribute("data-size", "normal");
render(<Dashboard />);
const main = screen.getByRole("main");
expect(within(main).getByRole("button", { name: "Larger text" })).toBeInTheDocument();
expect(within(main).getByLabelText("Search teas")).toBeInTheDocument();
expect(within(main).getByText("Mon: 42 cups")).toBeInTheDocument();
let chartRenders = 0;
render(<Dashboard onChartRender={() => chartRenders++} />);
expect(screen.getByText("Sat: 80 cups")).toBeInTheDocument();
expect(chartRenders).toBe(1);
let chartRenders = 0;
render(<Dashboard onChartRender={() => chartRenders++} />);
await user.type(screen.getByLabelText("Search teas"), "oolong");
expect(screen.getByText("1 match")).toBeInTheDocument();
expect(chartRenders).toBe(1);
let chartRenders = 0;
render(<Dashboard onChartRender={() => chartRenders++} />);
await user.click(screen.getByRole("button", { name: "Larger text" }));
await user.click(screen.getByRole("button", { name: "Larger text" }));
expect(screen.getByRole("main")).toHaveAttribute("data-size", "normal");
expect(chartRenders).toBe(1);
render(<Dashboard teas={["Sencha", "Milk Oolong", "Rooibos"]} />);
await user.type(screen.getByLabelText("Search teas"), "oo");
await user.click(screen.getByRole("button", { name: "Larger text" }));
expect(screen.getByLabelText("Search teas")).toHaveValue("oo");
expect(screen.getByText("2 matches")).toBeInTheDocument();
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 Move state away from a slow chart. 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.