Skip to content

Commit 286f14a

Browse files
authored
Implement download without react-dom/server (adazzle#3613)
* Implement download without `react-dom/server'` * Use `flushSync` * Remove `ExportButton` component * Inline grid element * Address comments * Address comments
1 parent 4c4af3d commit 286f14a

File tree

2 files changed

+60
-77
lines changed

2 files changed

+60
-77
lines changed

website/exportUtils.tsx

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,17 @@
1-
import { cloneElement } from 'react';
2-
import type { ReactElement } from 'react';
3-
4-
import type { DataGridProps } from '../src';
5-
6-
export async function exportToCsv<R, SR>(
7-
gridElement: ReactElement<DataGridProps<R, SR>>,
8-
fileName: string
9-
) {
10-
const { head, body, foot } = await getGridContent(gridElement);
1+
export function exportToCsv(gridEl: HTMLDivElement, fileName: string) {
2+
const { head, body, foot } = getGridContent(gridEl);
113
const content = [...head, ...body, ...foot]
124
.map((cells) => cells.map(serialiseCellValue).join(','))
135
.join('\n');
146

157
downloadFile(fileName, new Blob([content], { type: 'text/csv;charset=utf-8;' }));
168
}
179

18-
export async function exportToPdf<R, SR>(
19-
gridElement: ReactElement<DataGridProps<R, SR>>,
20-
fileName: string
21-
) {
22-
const [{ jsPDF }, autoTable, { head, body, foot }] = await Promise.all([
10+
export async function exportToPdf(gridEl: HTMLDivElement, fileName: string) {
11+
const { head, body, foot } = getGridContent(gridEl);
12+
const [{ jsPDF }, { default: autoTable }] = await Promise.all([
2313
import('jspdf'),
24-
(await import('jspdf-autotable')).default,
25-
await getGridContent(gridElement)
14+
import('jspdf-autotable')
2615
]);
2716
const doc = new jsPDF({
2817
orientation: 'l',
@@ -40,23 +29,15 @@ export async function exportToPdf<R, SR>(
4029
doc.save(fileName);
4130
}
4231

43-
async function getGridContent<R, SR>(gridElement: ReactElement<DataGridProps<R, SR>>) {
44-
const { renderToStaticMarkup } = await import('react-dom/server');
45-
const grid = document.createElement('div');
46-
grid.innerHTML = renderToStaticMarkup(
47-
cloneElement(gridElement, {
48-
enableVirtualization: false
49-
})
50-
);
51-
32+
function getGridContent(gridEl: HTMLDivElement) {
5233
return {
5334
head: getRows('.rdg-header-row'),
5435
body: getRows('.rdg-row:not(.rdg-summary-row)'),
5536
foot: getRows('.rdg-summary-row')
5637
};
5738

5839
function getRows(selector: string) {
59-
return Array.from(grid.querySelectorAll<HTMLDivElement>(selector)).map((gridRow) => {
40+
return Array.from(gridEl.querySelectorAll<HTMLDivElement>(selector)).map((gridRow) => {
6041
return Array.from(gridRow.querySelectorAll<HTMLDivElement>('.rdg-cell')).map(
6142
(gridCell) => gridCell.innerText
6243
);

website/routes/CommonFeatures.lazy.tsx

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useMemo, useState } from 'react';
2-
import { createPortal } from 'react-dom';
1+
import { useMemo, useRef, useState } from 'react';
2+
import { createPortal, flushSync } from 'react-dom';
33
import { faker } from '@faker-js/faker';
44
import { createLazyFileRoute } from '@tanstack/react-router';
55
import { css } from '@linaria/core';
@@ -9,6 +9,7 @@ import DataGrid, {
99
SelectColumn,
1010
textEditor,
1111
type Column,
12+
type DataGridHandle,
1213
type SortColumn
1314
} from '../../src';
1415
import { textEditorClassname } from '../../src/editors/textEditor';
@@ -310,6 +311,8 @@ function CommonFeatures() {
310311
const [rows, setRows] = useState(createRows);
311312
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
312313
const [selectedRows, setSelectedRows] = useState((): ReadonlySet<number> => new Set());
314+
const [isExporting, setIsExporting] = useState(false);
315+
const gridRef = useRef<DataGridHandle>(null);
313316

314317
const countries = useMemo((): readonly string[] => {
315318
return [...new Set(rows.map((r) => r.country))].sort(new Intl.Collator().compare);
@@ -342,61 +345,60 @@ function CommonFeatures() {
342345
});
343346
}, [rows, sortColumns]);
344347

345-
const gridElement = (
346-
<DataGrid
347-
rowKeyGetter={rowKeyGetter}
348-
columns={columns}
349-
rows={sortedRows}
350-
defaultColumnOptions={{
351-
sortable: true,
352-
resizable: true
353-
}}
354-
selectedRows={selectedRows}
355-
onSelectedRowsChange={setSelectedRows}
356-
onRowsChange={setRows}
357-
sortColumns={sortColumns}
358-
onSortColumnsChange={setSortColumns}
359-
topSummaryRows={summaryRows}
360-
bottomSummaryRows={summaryRows}
361-
className="fill-grid"
362-
direction={direction}
363-
/>
364-
);
348+
function handleExportToCsv() {
349+
flushSync(() => {
350+
setIsExporting(true);
351+
});
352+
353+
exportToCsv(gridRef.current!.element!, 'CommonFeatures.csv');
354+
355+
flushSync(() => {
356+
setIsExporting(false);
357+
});
358+
}
359+
360+
async function handleExportToPdf() {
361+
flushSync(() => {
362+
setIsExporting(true);
363+
});
364+
365+
await exportToPdf(gridRef.current!.element!, 'CommonFeatures.pdf');
366+
367+
flushSync(() => {
368+
setIsExporting(false);
369+
});
370+
}
365371

366372
return (
367373
<>
368374
<div className={toolbarClassname}>
369-
<ExportButton onExport={() => exportToCsv(gridElement, 'CommonFeatures.csv')}>
375+
<button type="button" onClick={handleExportToCsv}>
370376
Export to CSV
371-
</ExportButton>
372-
<ExportButton onExport={() => exportToPdf(gridElement, 'CommonFeatures.pdf')}>
377+
</button>
378+
<button type="button" onClick={handleExportToPdf}>
373379
Export to PDF
374-
</ExportButton>
380+
</button>
375381
</div>
376-
{gridElement}
382+
<DataGrid
383+
ref={gridRef}
384+
rowKeyGetter={rowKeyGetter}
385+
columns={columns}
386+
rows={sortedRows}
387+
defaultColumnOptions={{
388+
sortable: true,
389+
resizable: true
390+
}}
391+
selectedRows={selectedRows}
392+
onSelectedRowsChange={setSelectedRows}
393+
onRowsChange={setRows}
394+
sortColumns={sortColumns}
395+
onSortColumnsChange={setSortColumns}
396+
topSummaryRows={summaryRows}
397+
bottomSummaryRows={summaryRows}
398+
className="fill-grid"
399+
direction={direction}
400+
enableVirtualization={!isExporting}
401+
/>
377402
</>
378403
);
379404
}
380-
381-
function ExportButton({
382-
onExport,
383-
children
384-
}: {
385-
onExport: () => Promise<unknown>;
386-
children: React.ReactNode;
387-
}) {
388-
const [exporting, setExporting] = useState(false);
389-
return (
390-
<button
391-
type="button"
392-
disabled={exporting}
393-
onClick={async () => {
394-
setExporting(true);
395-
await onExport();
396-
setExporting(false);
397-
}}
398-
>
399-
{exporting ? 'Exporting' : children}
400-
</button>
401-
);
402-
}

0 commit comments

Comments
 (0)