Skip to content

Commit 2a2c317

Browse files
committed
[params] Demo update
1 parent 7a90214 commit 2a2c317

File tree

3 files changed

+85
-73
lines changed

3 files changed

+85
-73
lines changed

site/demos/07_car_analysis.md

Lines changed: 76 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
In this demo, we build an app that showcases the query capabilities of TinyBase
44
v2.0, grouping and sorting dimensional data for lightweight analytical usage.
55

6+
We've also updated it to use parameterized queries to take advantage of TinyBase
7+
v7.2.
8+
69
The data from this demo is derived from `cars.json` in the [Vega
710
datasets](https://github.com/vega/vega-datasets) - thank you [UW Interactive
811
Data Lab](https://idl.cs.washington.edu/)!
@@ -63,10 +66,12 @@ import {
6366
Provider,
6467
useCreateQueries,
6568
useCreateStore,
69+
useParamValue,
6670
useQueries,
6771
useResultCell,
6872
useResultSortedRowIds,
6973
useResultTable,
74+
useSetParamValueCallback,
7075
} from 'tinybase/ui-react';
7176
import {ResultSortedTableInHtmlTable} from 'tinybase/ui-react-dom';
7277
import {Inspector} from 'tinybase/ui-react-inspector';
@@ -130,10 +135,34 @@ app is rendered.
130135
```jsx
131136
const App = () => {
132137
const store = useCreateStore(createStore);
133-
const queries = useCreateQueries(store, createQueries);
134-
// ...
138+
const queries = useCreateQueries(store, () =>
139+
createQueries(store)
140+
// ...
141+
```
142+
143+
Since v7.2, we can define a single parameterized query for the whole application
144+
and then simply pass in new params as the user interacts with the UI. We're
145+
going to call this master query `cars`:
146+
147+
```js
148+
// ...
149+
.setQueryDefinition('cars', 'cars', ({select, where, group, param}) => {
150+
param('dimensions').forEach((cellId) => select(cellId));
151+
param('measures').forEach((cellId) => {
152+
select(cellId);
153+
group(cellId, AGGREGATES[param('aggregate')]);
154+
});
155+
}, {
156+
dimensions: ['Manufacturer'],
157+
measures: ['MPG', 'Horsepower'],
158+
aggregate: 'Average',
159+
}),
160+
);
135161
```
136162
163+
We will be able to set the 'dimensions', 'measures', and 'aggregate' params
164+
based on user interactions.
165+
137166
This application depends on loading data into the main Store the first time it
138167
is rendered. We do this by having an `isLoading` flag in the application's
139168
state, and setting it to `false` only once the asynchronous loading sequence in
@@ -242,31 +271,43 @@ left hand side that allows the user to select dimensions, measures, and the
242271
aggregation to be used.
243272

244273
The `Body` component wraps the sidebar and the field selection state, and then
245-
the `ResultGraph` and `ResultTable` components render the two modes.
246-
247-
The state of the application is based on selected dimensions and measures, which
248-
aggregate is used, and whether the data is rendered as a table. This is
249-
set up with the following hooks:
274+
the `ResultGraph` and `ResultTable` components render the two modes. We start
275+
off with a state variable indicating whether the data is rendered as a table
276+
(`true`) or a graph (`false`):
250277

251278
```js
252279
const Body = () => {
253-
const [dimensions, setDimensions] = useState(['Manufacturer']);
254-
const [measures, setMeasures] = useState(['MPG', 'Horsepower']);
255-
const [aggregate, setAggregate] = useState('Average');
256-
const [showTable, setAsGraph] = useState(false);
280+
const [showTable, setShowTable] = useState(false);
257281
// ...
258282
```
259283
260-
The first three of these state variables - `dimensions`, `measures`, and
261-
`aggregate` - are used to create a query from the `cars` Table, and group the
262-
fields accordingly. We run the memoized creation of the query in a separate
263-
hook (which we'll come to shortly), and get its Id so we can use it in the UI:
284+
The state of the application's result rendering is based on selected dimensions
285+
and measures, and which aggregate is used. These state variables - `dimensions`,
286+
`measures`, and `aggregate` - are used as the params to the `cars` query against
287+
the `cars` Table, and are used to group the fields accordingly.
288+
289+
Let's create three state-like pairs containing the values of these params and
290+
callbacks that will be used set them in the Queries object whenever they change
291+
in the application's sidebar:
264292
265293
```js
266-
const queryId = useBuildQuery(dimensions, measures, aggregate);
294+
const [dimensions, setDimensions] = [
295+
useParamValue('cars', 'dimensions'),
296+
useSetParamValueCallback('cars', 'dimensions', (dimensions) => dimensions),
297+
];
298+
299+
const [measures, setMeasures] = [
300+
useParamValue('cars', 'measures'),
301+
useSetParamValueCallback('cars', 'measures', (measures) => measures),
302+
];
303+
304+
const [aggregate, setAggregate] = [
305+
useParamValue('cars', 'aggregate'),
306+
useSetParamValueCallback('cars', 'aggregate', (aggregate) => aggregate),
307+
];
267308
```
268309
269-
Here we render the left-hand sidebar, showing the available dimensions,
310+
Next we render the left-hand sidebar, showing the available dimensions,
270311
measures, and aggregates. We also show a toggle for the tabular view, and offer
271312
a link to the source of the data. (We'll also cover the simple `Select`
272313
component later.)
@@ -301,7 +342,7 @@ return (
301342
id="showTable"
302343
type="checkbox"
303344
checked={showTable}
304-
onChange={useCallback(({target}) => setAsGraph(target.checked), [])}
345+
onChange={useCallback(({target}) => setShowTable(target.checked), [])}
305346
/>
306347
<label for="showTable">Show table</label>
307348
<br />
@@ -311,57 +352,25 @@ return (
311352
</a>
312353
</small>
313354
</aside>
314-
{/* ... */}
315355
```
316356
317357
We complete the `Body` component with a simple toggle between the two main
318358
views, which of course we will also shortly explore in detail. Both take the
319-
query Id and the columns to display:
359+
list of columns to display:
320360
321361
```jsx
322-
{/* ... */}
323-
{showTable ? (
324-
<ResultTable
325-
queryId={queryId}
326-
columns={[...dimensions, ...measures]}
327-
/>
328-
) : (
329-
<ResultGraph
330-
queryId={queryId}
331-
dimensions={dimensions}
332-
measures={measures}
333-
/>
334-
)}
362+
{
363+
showTable ? (
364+
<ResultTable columns={[...dimensions, ...measures]} />
365+
) : (
366+
<ResultGraph dimensions={dimensions} measures={measures} />
367+
)
368+
}
335369
</>
336370
);
337371
};
338372
```
339373

340-
That's the main outer structure of the application, but a lot of the magic is in
341-
the `useBuildQuery` hook we mentioned earlier. Whenever the dimensions,
342-
measures, or aggregate selections change, we want to run a new query on the data
343-
accordingly. Here's the implementation:
344-
345-
```js
346-
const useBuildQuery = (dimensions, measures, aggregate) =>
347-
useMemo(() => {
348-
useQueries().setQueryDefinition('query', 'cars', ({select, group}) => {
349-
dimensions.forEach((cellId) => select(cellId));
350-
measures.forEach((cellId) => {
351-
select(cellId);
352-
group(cellId, AGGREGATES[aggregate]);
353-
});
354-
});
355-
return 'query';
356-
}, [dimensions, measures, aggregate]);
357-
```
358-
359-
The whole hook is memoized so that the query is not run every time the `Body`
360-
component is run. The query is (imaginatively) called `query`, and is against
361-
the `cars` Table in the main Store. The query simply selects every selected
362-
dimension, and every selected measure. Each measure is then grouped with the
363-
selected aggregate.
364-
365374
And that's it! Enough to query the dataset such that both the graphical and
366375
tabular views work. We'll dive into _their_ implementations next.
367376

@@ -409,9 +418,9 @@ new ui-react-dom module straight out of the box. The only extra step is
409418
transforming the array of selected columns into the customCells prop to render.
410419

411420
```jsx
412-
const ResultTable = ({queryId, columns}) => (
421+
const ResultTable = ({columns}) => (
413422
<ResultSortedTableInHtmlTable
414-
queryId={queryId}
423+
queryId="cars"
415424
sortOnClick={true}
416425
paginator={true}
417426
limit={10}
@@ -433,8 +442,8 @@ cellId as className so we can color the left-hand border.
433442

434443
```jsx
435444
// ...
436-
const CustomCell = ({queryId, rowId, cellId}) => {
437-
const cell = useResultCell(queryId, rowId, cellId);
445+
const CustomCell = ({rowId, cellId}) => {
446+
const cell = useResultCell('cars', rowId, cellId);
438447
return (
439448
<span className={cellId}>{Number.isFinite(cell) ? round(cell) : cell}</span>
440449
);
@@ -498,7 +507,7 @@ size of the component on the screen. To track this, we use the browser's
498507
`ResizeObserver` API, and set width and height state variables whenever it changes:
499508

500509
```jsx
501-
const ResultGraph = ({queryId, dimensions, measures}) => {
510+
const ResultGraph = ({dimensions, measures}) => {
502511
const ref = useRef(null);
503512
const [{width = 0, height = 0}, setDimensions] = useState({});
504513
useEffect(() => {
@@ -518,11 +527,7 @@ present so we can decide how high to make the y-axis:
518527

519528
```jsx
520529
// ...
521-
const [xAllLabels, yValueSets, yMax] = useGraphData(
522-
queryId,
523-
dimensions,
524-
measures,
525-
);
530+
const [xAllLabels, yValueSets, yMax] = useGraphData(dimensions, measures);
526531
// ...
527532
```
528533

@@ -613,7 +618,6 @@ hidden) `<circle />` and `<text />` elements tht serve as hover-over labels for
613618
each data point:
614619
615620
```jsx
616-
{/* ... */}
617621
{yValueSets.map((yValueSet, s) => (
618622
<g className={measures[s]} key={s}>
619623
<path
@@ -722,9 +726,9 @@ Note that the Row Ids are sorted according to the values of the first measure
722726
selected. In a real analytical scenario, this would be better to be configurable.
723727

724728
```js
725-
const useGraphData = (queryId, dimensions, measures) => {
726-
const table = useResultTable(queryId);
727-
const sortedRowIds = useResultSortedRowIds(queryId, measures[0] ?? undefined);
729+
const useGraphData = (dimensions, measures) => {
730+
const table = useResultTable('cars');
731+
const sortedRowIds = useResultSortedRowIds('cars', measures[0] ?? undefined);
728732
return useMemo(() => {
729733
const yAll = [1];
730734
const xAllLabels = [];

site/demos/08_movie_database.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ In this demo, we build an app that showcases the relational query capabilities
44
of TinyBase v2.0, joining together information about movies, directors, and
55
actors.
66

7+
We've also updated it to use parameterized queries to take advantage of TinyBase
8+
v7.2.
9+
710
![TMDB](https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg 'Inline logo for TMDB') - we use The Movie Database as the source of the movie
811
information in this app. Thank you for a great data set to demonstrate TinyBase!
912

site/guides/16_releases.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ result is more efficient and easier to (we think) understand. See the
6969
`yearGenreMovies`, `directedMovies`, and `appearedMovies` queries to see
7070
params in action.
7171

72+
We have also updated the Car Analysis demo to use just one single parameterized
73+
query for the whole app!
74+
7275
## Full API additions
7376

7477
This release includes the following new Queries interface methods:
@@ -89,7 +92,9 @@ It also includes the following new React hooks:
8992
- useParamValuesListener hook
9093
- useParamValueListener hook
9194

92-
Check out the API docs for each. They should seem very familiar!
95+
Check out the API docs for each. They should seem very familiar.
96+
97+
Please check out this new release and let us know what you think!
9398

9499
---
95100

0 commit comments

Comments
 (0)