Skip to content

Commit

Permalink
feat: store table state
Browse files Browse the repository at this point in the history
  • Loading branch information
schummar committed Feb 1, 2022
1 parent 152112c commit dabdae9
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 1 deletion.
3 changes: 3 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { Action } from 'schummar-state/react';
import { DefaultFilterComponent, Table, TextFilterComponent } from '../../src';
import { flatMap } from '../../src/misc/helpers';
import { configureTables } from '../../src/table';
import localforage from 'localforage';

const storage = localforage.createInstance({ name: 'xyz' });

configureTables({
text: {
Expand Down
49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"global-jsdom": "^8.3.0",
"immer": "*",
"jsdom": "^18.1.0",
"localforage": "^1.10.0",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.4.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const Row = memo(function Row<T>({ itemId, rowIndex }: { itemId: Id; rowI
hasDeferredChildren: item && state.props.hasDeferredChildren?.(item),
columnIds: state.activeColumns.map((column) => column.id),
enableSelection: state.props.enableSelection,
rowAction: state.props.rowAction instanceof Function ? state.props.rowAction(item, index) : state.props.rowAction,
rowAction: state.props.rowAction instanceof Function ? (item ? state.props.rowAction(item, index) : null) : state.props.rowAction,
};
},
[itemId],
Expand Down
86 changes: 86 additions & 0 deletions src/internalState/tableStateStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useEffect } from 'react';
import { useTableContext } from '..';
import { Queue } from '../misc/queue';

const KEYS = ['sort', 'selection', 'expanded', 'hiddenColumns'] as const;

export type TableStateStorage = {
getItem: (key: string) => string | null | Promise<string | null>;
setItem: (key: string, value: string) => unknown | Promise<unknown>;
removeItem: (key: string) => unknown | Promise<unknown>;
} & (
| {
keys: () => string[] | Promise<string[]>;
}
| {
length: number | (() => number | Promise<number>);
key: (keyIndex: number) => string | null | Promise<string | null>;
}
);

export function useTableStateStorage() {
const state = useTableContext();

// On mount: load
useEffect(() => {
const { storeState, debug } = state.getState().props;
if (!storeState) return;

let isCanceled = false;
const q = new Queue();

q.run(async () => {
try {
const { storage, id = 'table', include } = storeState;
const json = await storage.getItem(`${id}_state`);
if (isCanceled || !json) return;

const data = JSON.parse(json);

state.update((state) => {
for (const key of KEYS) {
if (!include || include[key]) {
const value = key === 'sort' ? data.sort : new Set(data[key]);
state[key] = value;
}
}
});
} catch (e) {
debug?.('Failed to load table state:', e);
}
});

state.subscribe(
(state) => {
if (!state.props.storeState) return;

const data: any = {};

for (const key of KEYS) {
if (!state.props.storeState.include || state.props.storeState.include[key]) {
const value = key === 'sort' ? state.sort : Array.from(state[key]);
data[key] = value;
}
}

return {
storage: state.props.storeState.storage,
id: state.props.storeState.id,
data,
};
},
(props) => {
if (!props) return;
const { storage, id = 'table', data } = props;

q.run(async () => {
await storage.setItem(`${id}_state`, JSON.stringify(data));
});
},
);

return () => {
isCanceled = true;
};
}, []);
}
28 changes: 28 additions & 0 deletions src/misc/queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export class Queue {
private q = new Array<{ job: () => Promise<any>; resolve: (value: any) => void; reject: (reason?: any) => void }>();
private isRunning = false;

run<T>(job: () => Promise<T>) {
return new Promise<T>((resolve, reject) => {
this.q.push({ job, resolve, reject });

if (!this.isRunning) this.start();
});
}

private async start() {
this.isRunning = true;

let next;
while ((next = this.q.shift())) {
try {
const result = await next.job();
next.resolve(result);
} catch (e) {
next.reject(e);
}
}

this.isRunning = false;
}
}
2 changes: 2 additions & 0 deletions src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SelectComponent } from './components/selectComponent';
import { SortComponent } from './components/sortComponent';
import { useCommonClasses } from './components/useCommonClasses';
import { Virtualized } from './components/virtualized';
import { useTableStateStorage } from './internalState/tableStateStorage';
import { useTableState } from './internalState/useTableState';
import { c } from './misc/helpers';
import { Id, InternalTableState, TableProps } from './types';
Expand Down Expand Up @@ -50,6 +51,7 @@ export function Table<T>(props: TableProps<T>): JSX.Element {
}

const TableInner = memo(function TableInner<T>(): JSX.Element {
useTableStateStorage();
const commonClasses = useCommonClasses();
const state = useTableContext<T>();
const fullWidth = state.useState('props.fullWidth');
Expand Down
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CSSProperties, ReactNode } from 'react';
import { Filter } from './components/filterComponent';
import { CsvExportOptions } from './misc/csvExport';
import { TableStateStorage } from './internalState/tableStateStorage';

export type Sort = { columnId: string | number; direction: SortDirection };
export type SortDirection = 'asc' | 'desc';
Expand Down Expand Up @@ -70,6 +71,17 @@ export type TableProps<T> = {
enableSelection?: boolean;
enableColumnSelection?: boolean;
enableExport?: boolean | { copy?: boolean | CsvExportOptions; download?: boolean | CsvExportOptions };

storeState?: {
storage: TableStateStorage;
id?: string;
include?: {
sort?: boolean;
selection?: boolean;
expanded?: boolean;
hiddenColumns?: boolean;
};
};
};

export type InternalTableProps<T> = Omit<
Expand Down

0 comments on commit dabdae9

Please sign in to comment.