Skip to content

Commit e495a1d

Browse files
committed
[state] useTableState
1 parent 484a540 commit e495a1d

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

src/@types/ui-react/docs.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,68 @@
10161016
* @since v1.0.0
10171017
*/
10181018
/// useTable
1019+
/**
1020+
* The useTableState hook returns a Table and a function to set it, following
1021+
* the same pattern as React's useState hook.
1022+
*
1023+
* This is a convenience hook that combines the useTable and useSetTableCallback
1024+
* hooks. It's useful when you need both read and write access to a Table in a
1025+
* single component.
1026+
*
1027+
* A Provider component is used to wrap part of an application in a context,
1028+
* and it can contain a default Store or a set of Store objects named by Id.
1029+
* The useTableState hook lets you indicate which Store to use: omit the final
1030+
* parameter for the default context Store, provide an Id for a named context
1031+
* Store, or provide a Store explicitly by reference.
1032+
* @param tableId The Id of the Table in the Store.
1033+
* @param storeOrStoreId The Store to be accessed: omit for the default
1034+
* context Store, provide an Id for a named context Store, or provide an
1035+
* explicit reference.
1036+
* @returns An array containing the Table and a function to set it.
1037+
* @example
1038+
* This example creates a Store outside the application, which is used in the
1039+
* useTableState hook by reference. A button updates the Table when clicked.
1040+
*
1041+
* ```jsx
1042+
* import {createStore} from 'tinybase';
1043+
* import React from 'react';
1044+
* import {createRoot} from 'react-dom/client';
1045+
* import {useTableState} from 'tinybase/ui-react';
1046+
*
1047+
* const store = createStore().setTable('pets', {fido: {species: 'dog'}});
1048+
* const App = () => {
1049+
* const [table, setTable] = useTableState('pets', store);
1050+
* return (
1051+
* <div>
1052+
* {JSON.stringify(table)}
1053+
* <button onClick={() => setTable({...table, felix: {species: 'cat'}})}>
1054+
* Add
1055+
* </button>
1056+
* </div>
1057+
* );
1058+
* };
1059+
*
1060+
* const app = document.createElement('div');
1061+
* const root = createRoot(app);
1062+
* root.render(<App />); // !act
1063+
* console.log(app.innerHTML);
1064+
* // -> '<div>{"fido":{"species":"dog"}}<button>Add</button></div>'
1065+
*
1066+
* const button = app.querySelector('button');
1067+
* // -> button MouseEvent('click', {bubbles: true})
1068+
* console.log(app.innerHTML);
1069+
* // ->
1070+
* `
1071+
* <div>
1072+
* {"fido":{"species":"dog"},"felix":{"species":"cat"}}
1073+
* <button>Add</button>
1074+
* </div>
1075+
* `;
1076+
* ```
1077+
* @category State hooks
1078+
* @since v7.3.0
1079+
*/
1080+
/// useTableState
10191081
/**
10201082
* The useTableCellIds hook returns the Ids of every Cell used across the whole
10211083
* Table, and registers a listener so that any changes to that result will cause

src/@types/ui-react/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ export function useHasTable(
163163
/// useTable
164164
export function useTable(tableId: Id, storeOrStoreId?: StoreOrStoreId): Table;
165165

166+
/// useTableState
167+
export function useTableState(
168+
tableId: Id,
169+
storeOrStoreId?: StoreOrStoreId,
170+
): [Table, (table: Table) => void];
171+
166172
/// useTableCellIds
167173
export function useTableCellIds(
168174
tableId: Id,

src/@types/ui-react/with-schemas/index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ export type WithSchemas<Schemas extends OptionalSchemas> = {
216216
storeOrStoreId?: StoreOrStoreId<Schemas>,
217217
) => Table<Schemas[0], TableId>;
218218

219+
/// useTableState
220+
useTableState: <TableId extends TableIdFromSchema<Schemas[0]>>(
221+
tableId: TableId,
222+
storeOrStoreId?: StoreOrStoreId<Schemas>,
223+
) => [
224+
Table<Schemas[0], TableId>,
225+
(table: Table<Schemas[0], TableId>) => void,
226+
];
227+
219228
/// useTableCellIds
220229
useTableCellIds: <TableId extends TableIdFromSchema<Schemas[0]>>(
221230
tableId: TableId,

src/ui-react/hooks.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ import type {
239239
useTableIds as useTableIdsDecl,
240240
useTableIdsListener as useTableIdsListenerDecl,
241241
useTableListener as useTableListenerDecl,
242+
useTableState as useTableStateDecl,
242243
useTables as useTablesDecl,
243244
useTablesListener as useTablesListenerDecl,
244245
useUndoInformation as useUndoInformationDecl,
@@ -670,6 +671,14 @@ export const useTable: typeof useTableDecl = (
670671
tableId,
671672
]);
672673

674+
export const useTableState: typeof useTableStateDecl = (
675+
tableId: Id,
676+
storeOrStoreId?: StoreOrStoreId,
677+
): [Table, (table: Table) => void] => [
678+
useTable(tableId, storeOrStoreId),
679+
useSetTableCallback(tableId, (table) => table, [], storeOrStoreId),
680+
];
681+
673682
export const useTableCellIds: typeof useTableCellIdsDecl = (
674683
tableId: Id,
675684
storeOrStoreId?: StoreOrStoreId,

test/unit/core/ui-react/hooks.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ import {
152152
useTableIds,
153153
useTableIdsListener,
154154
useTableListener,
155+
useTableState,
155156
useTables,
156157
useTablesListener,
157158
useUndoInformation,
@@ -1174,6 +1175,37 @@ describe('Read Hooks', () => {
11741175
unmount();
11751176
});
11761177

1178+
test('useTableState', () => {
1179+
const Test = () => {
1180+
const [table, setTable] = useTableState('t1', store);
1181+
return (
1182+
<span>
1183+
{JSON.stringify(table)}
1184+
<button onClick={() => setTable({...table, r2: {c1: 2}})} />
1185+
</span>
1186+
);
1187+
};
1188+
1189+
store.setTable('t1', {r1: {c1: 1}});
1190+
const {container, unmount} = render(<Test />);
1191+
1192+
expect(container.innerHTML).toEqual(
1193+
'<span>{"r1":{"c1":1}}<button></button></span>',
1194+
);
1195+
1196+
act(() => fireEvent.click(container.querySelector('button') as Element));
1197+
expect(container.innerHTML).toEqual(
1198+
'<span>{"r1":{"c1":1},"r2":{"c1":2}}<button></button></span>',
1199+
);
1200+
1201+
act(() => fireEvent.click(container.querySelector('button') as Element));
1202+
expect(container.innerHTML).toEqual(
1203+
'<span>{"r1":{"c1":1},"r2":{"c1":2}}<button></button></span>',
1204+
);
1205+
1206+
unmount();
1207+
});
1208+
11771209
test('useTableCellIds', () => {
11781210
const Test = ({tableId}: {tableId: Id}) =>
11791211
didRender(JSON.stringify(useTableCellIds(tableId, store)));

0 commit comments

Comments
 (0)