I searched for a whale, but got a cat
How to bring 10,000 whales onto the screen
Goal
You write a search form, status messages, and a list viewed 50 items at a time yourself, in React+TypeScript. You check that input and focus are preserved even when the screen updates, and operate it directly in the browser.
Why it matters
Even if you receive the correct response, a screen is unusable if the input has no name or can't be submitted with the keyboard. Guidance for errors and empty results helps users choose their next action. Large data creates network receiving cost and DOM rendering cost separately. Hiding all the data with CSS is different from actually drawing only part of it. You need to be able to read JSX, props, useState, conditional rendering, and TypeScript unions.
Preparation
Run node /opt/react-workbench/prepare.mjs /root/react-ui-lab ui once at the start.
If the directory already exists, keep using your current work and do not overwrite it. No npm install is needed.
The file to edit is /root/react-ui-lab/src/SearchView.tsx, and the type material is /root/react-ui-lab/src/types.ts.
SearchView is a named export that receives state (SearchState), revision (number), and onSearch(text) as props.
A success SearchState has one or more items, and zero results is empty. The revision changes every time results are newly injected.
The state injection device on the right provides synthetic data without HTTP. You can practice the UI without rewriting the hook answer.
Steps
- Give the search box a name — Prepare /root/react-ui-lab in ui mode and write src/SearchView.tsx. Keep the SearchView named export and the provided props. Create a form with role search, an input of at most 80 characters connected to a label whose text is the Korean word for "search term", and a submit button whose text is the Korean word for "search". Manage the input value in the component. At this step, do not wire up submit handling yet.
- Enter sends the same search too — Prevent the default reload on form submission and pass the input value, with leading and trailing whitespace removed, to onSearch(text). Call it every time even if the same word is submitted consecutively, and if only whitespace is entered, pass an empty string. Keep the text being typed as it is, and do not depend on button clicks alone.
- Tell an empty library from a broken library — In the status message area that is always present, set role=status, aria-live=polite, and aria-atomic=true. Describe idle, loading, empty, error, and success in different Korean sentences. Show the corresponding query for loading, empty results, and success, and the passed message for failure. When the state is not normal, do not leave the previous result list.
- Show the data as text, not as code — For each item in success, show the title and detail in the original order. Create a ul whose name is the Korean phrase for "search results list" and set tabIndex=0 so that the list can also receive focus. Even if a string that looks like HTML arrives in a title, do not execute it as tags; show it as plain text. At this step, it is fine to show the whole list.
- Meet 10,000 whales 50 at a time — While preserving the original items, draw at most 50 items in the DOM at a time. Change the displayed range with default buttons whose names are the Korean words for "previous page" and "next page". With 10,000 items, the next page starts at the 51st item, and pressing previous again must return to the first item. Approaches that only change numbers or CSS display and leave everything in the DOM are excluded.
- Don't fall off past the last page — Group the page controls inside a nav whose name is the Korean phrase for "result pages". Previous on the first page and next on the last page must be disabled. With 51 items, the last page has 1 item, and with exactly 50 items there is one page. Use the contract that success.items is never empty and a zero-result outcome is delivered as empty.
- Don't attach an old page number to a new search — When the revision changes, show the results from the first page. A re-search with the same query is also a new revision. Even when switching from the third page to a new result of 3 items, the first item must appear, not a blank screen. The lifetimes of the text being typed and of the search form are separated from the result list.
- Keep the finger's place even when the screen updates — Check that even when new results or a failure arrive during typing, the input DOM, the text being written, and the focus are kept. Keep the status message DOM too and update only the wording. Recheck all the contracts from the earlier steps. After building, check Tab and Enter, page navigation, and the 390px screen yourself in the preview. This is a regression step, so a correct implementation of step 7 can pass it.
Notes
Run cd /root/react-ui-lab first, then npm run typecheck, npm run build, and npm start in that order.
In the preview address http://127.0.0.1:3000/, keep the final /. After editing the source, rebuild and refresh.
Step 1 has no form submit handling yet, so Enter may reload the page. You wire it up in step 2.
You can check the whole cumulative contract with node /opt/react-workbench/grading/check-step.mjs /root/react-ui-lab/src/SearchView.tsx 8 ui.
Each step also checks the earlier steps. A limit of 128KiB for ordinary source and 12 seconds for the work process applies, and grading does not modify the student file.
The DOM check in this lab is not certification of CSS visibility, color contrast, or real screen reader narration.
Even if you display 50 items at a time, the array you received stays in memory. Observe HTTP time, React Profiler actualDuration, layout, and paint separately.
If needed, extend with the +time button, and save your source separately before it ends. Files are not kept after the lab session ends.
Give the search box a name
Prepare /root/react-ui-lab in ui mode and write src/SearchView.tsx. Keep the SearchView named export and the provided props. Create a form with role search, an input of at most 80 characters connected to a label whose text is the Korean word for "search term", and a submit button whose text is the Korean word for "search". Manage the input value in the component. At this step, do not wire up submit handling yet.
Writing a label on the screen is different from connecting it to the input. Use an htmlFor/id pair or put the input inside the label. Don't substitute a placeholder alone.
Enter sends the same search too
Prevent the default reload on form submission and pass the input value, with leading and trailing whitespace removed, to onSearch(text). Call it every time even if the same word is submitted consecutively, and if only whitespace is entered, pass an empty string. Keep the text being typed as it is, and do not depend on button clicks alone.
Create the search intent from the form submission, not from the input event. If you handle it in onSubmit, the default submit button and Enter use the same path.
Tell an empty library from a broken library
In the status message area that is always present, set role=status, aria-live=polite, and aria-atomic=true. Describe idle, loading, empty, error, and success in different Korean sentences. Show the corresponding query for loading, empty results, and success, and the passed message for failure. When the state is not normal, do not leave the previous result list.
Empty means the search succeeded but there is no data, and error means the request failed. If you merge them into the same message, the user can't decide whether to change the search term or retry.
Show the data as text, not as code
For each item in success, show the title and detail in the original order. Create a ul whose name is the Korean phrase for "search results list" and set tabIndex=0 so that the list can also receive focus. Even if a string that looks like HTML arrives in a title, do not execute it as tags; show it as plain text. At this step, it is fine to show the whole list.
If you put a value in a text position in JSX, the data is not interpreted as HTML. Use the data's stable id for the list key, and don't forget the detail.
Meet 10,000 whales 50 at a time
While preserving the original items, draw at most 50 items in the DOM at a time. Change the displayed range with default buttons whose names are the Korean words for "previous page" and "next page". With 10,000 items, the next page starts at the 51st item, and pressing previous again must return to the first item. Approaches that only change numbers or CSS display and leave everything in the DOM are excluded.
Compute the start and end of the slice from the current page number. Distinguish that the amount of data received stays the same and only the number of rows to render shrinks.
Don't fall off past the last page
Group the page controls inside a nav whose name is the Korean phrase for "result pages". Previous on the first page and next on the last page must be disabled. With 51 items, the last page has 1 item, and with exactly 50 items there is one page. Use the contract that success.items is never empty and a zero-result outcome is delivered as empty.
Rounding a division up gives the number of pages needed. The last index and the page count differ by 1. Instead of only dimming the color, use the real disabled attribute.
Don't attach an old page number to a new search
When the revision changes, show the results from the first page. A re-search with the same query is also a new revision. Even when switching from the third page to a new result of 3 items, the first item must appear, not a blank screen. The lifetimes of the text being typed and of the search form are separated from the result list.
If you recreate only the child component for results with the new revision, you can reset the page state. If you recreate even the form with a key, the input and focus disappear along with it.
Keep the finger's place even when the screen updates
Check that even when new results or a failure arrive during typing, the input DOM, the text being written, and the focus are kept. Keep the status message DOM too and update only the wording. Recheck all the contracts from the earlier steps. After building, check Tab and Enter, page navigation, and the 390px screen yourself in the preview. This is a regression step, so a correct implementation of step 7 can pass it.
Don't forcibly move focus to the results. The automated DOM check and real-browser keyboard and layout observation are different kinds of evidence, and neither certifies screen reader usability.