Feat table export - #10
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
useTableExport, a hook that exports any tablecraft table to CSV or JSON.It returns the data (
toRows,toCSV,toJSON) plus adownload()helper forthe common case, and ships from the root entry because it adds no
dependency — CSV and JSON are pure string generation.
Exports respect the active filters, sort order, and column visibility.
rows: 'selected'narrows to the selected rows.Why
Export is the most common thing consumers build by hand on top of a table, and
doing it correctly is fiddlier than it looks: RFC 4180 quoting, duplicate column
headers, JSX-rendering cells, grouped rows, and Excel's BOM requirement all have
non-obvious right answers. This puts them in one place.
Design decisions worth reviewing
Row scope reads
getPrePaginationRowModel(), notgetFilteredRowModel().This is the most important detail in the diff. TanStack's pipeline is
core → filtered → grouped → sorted → expanded → paginated, so
getFilteredRowModel()returns rows in original data order and silentlydiscards the user's sort. Someone sorts by revenue, hits Export, and gets an
unsorted file with no error. There is a named regression test.
The UTF-8 BOM belongs to
download(), nevertoCSV(). Excel misreadsaccented characters in a BOM-less UTF-8 CSV, so the file path needs one. But a
BOM is an invisible leading character that corrupts an API upload and breaks
snapshot tests, so the string path must not have one. The split is on
"produces a file" vs "produces a string". Overridable with
{ bom: false }.Cell renderers are never invoked. Exports use the raw accessor value, with a
per-column override via
meta: { exportValue: (row) => … }. Rendering cells totext would need
react-dom/serverand would turn an action-button column intothe word "Delete" and an avatar column into an empty string. There is a test
asserting zero renderer calls.
download()throws during SSR;toRows/toCSV/toJSONare SSR-safe.Deliberately unlike
src/utils/url.ts, which swallows SSR errors silently — URLsync runs automatically in an effect on every render, so silence is correct
there, whereas
download()only ever runs from a click handler, so reaching itduring SSR is a genuine bug and should say so.
CSV and JSON only, no Excel. Every
.xlsxwriter is heavy (90 KB–950 KB)and SheetJS carries licensing and CVE baggage. A correctly encoded CSV opens in
Excel. If real
.xlsxis ever wanted it belongs behind its own subpath entrywith its own optional peer, following the
/queryand/virtualprecedent.Three bugs found and fixed during the build
All three originated in the implementation plan, not the code written from it:
dedupeLabelsdidn't de-duplicate. It counted repeats per originallabel, so
['Name','Name','Name (2)']emitted'Name (2)'twice — the exactcollision it exists to prevent. Fixed by tracking emitted labels.
as 6.
flatRowslists each leaf twice under grouping (once top-level, onceas a sub-row of its group header), so filtering out group headers left both
copies. Fixed by de-duping on
row.id, preserving first-seen order.Blob.text()performs aWHATWG UTF-8 decode that strips a leading BOM, which also made the two
negative BOM tests pass vacuously. All three now assert raw bytes via
FileReader.readAsArrayBuffer.Testing
check-entry-deps: OKconfirms the root entry's dependency boundary isunchanged — the export hook adds nothing that reaches an optional peer.
Two test groups are deliberately strict and should not be relaxed: the grouped
export asserts exact count and exact order (set-based and
toBeGreaterThanassertions passed happily with duplicated rows and hid bug 2), and the BOM tests
assert raw bytes for the reason above.
Breaking changes
None. Additive only.
Note for the reviewer
toRows()keys its objects by column header (de-duplicated) rather than bycolumn id. This is a genuine coin-flip that has never been explicitly ratified,
and it becomes public API on merge. Header keys give readable output
(
{ "First Name": "Ada" }); column-id keys would be stable across header andi18n changes (
{ firstName: "Ada" }). Worth settling in review.package.jsonis still3.0.0here — the version bump lives in the a11y PR.