Skip to content

Commit b1a6520

Browse files
committed
[params] More movies improvements
1 parent a0f1762 commit b1a6520

1 file changed

Lines changed: 131 additions & 118 deletions

File tree

site/demos/08_movie_database.md

Lines changed: 131 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,29 @@ These act as de-normalized 'views' of the underlying normalized data and make it
4343
easy for the application to render 'virtual' rows comprised of Cell values from
4444
multiple joined Table objects in the Store.
4545

46-
Some of these, like the main `movies` query, are set up for the lifetime of the
47-
application:
48-
49-
| Query | From Tables | Cell Ids |
50-
| ----------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51-
| `movies` | `movies`, `genres`, `people` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName`, `overview`, `directorId`, `directorName`, `directorImage`, `castId1`, `castName1`, `castImage1`, `castId2`, `castName2`, `castImage2`, `castId3`, `castName3`, `castImage3` |
52-
| `years` | `movies` | `year`, `movieCount` |
53-
| `genres` | `movies` | `genreId`, `genreName`, `movieCount` |
54-
| `directors` | `movies`, `people` | `directorId`, `directorName`, `directorImage`, `gender`, `popularity`, `movieCount` |
55-
| `cast` | `cast`, `people` | `castId`, `castName`, `castImage`, `gender`, `popularity`, `movieCount` |
56-
57-
Others, like the `moviesInYear` query, are set up when a specific page is being
58-
viewed (in that case, the detail page for a particular year):
59-
60-
| Query | From Tables | Cell Ids |
61-
| -------------------- | -------------------------- | ------------------------------------------------------------------------------ |
62-
| `moviesInYear` | `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
63-
| `moviesInGenre` | `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
64-
| `moviesWithDirector` | `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
65-
| `moviesWithCast` | `cast`, `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
66-
67-
You might notice that many of these queries share the same Cell Ids. You'll
68-
discover that TinyBase lets you compose queries programmatically, so we'll be
69-
able to build these queries without much repetition: the common
46+
The following queries are set up for the lifetime of the application:
47+
48+
| Query | From Tables | Cell Ids |
49+
| ----------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50+
| `movies` | `movies`, `genres`, `people` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName`, `overview`, `directorId`, `directorName`, `directorImage`, `castId1`, `castName1`, `castImage1`, `castId2`, `castName2`, `castImage2`, `castId3`, `castName3`, `castImage3` |
51+
| `years` | `movies` | `year`, `movieCount` |
52+
| `genres` | `movies` | `genreId`, `genreName`, `movieCount` |
53+
| `directors` | `movies`, `people` | `directorId`, `directorName`, `directorImage`, `gender`, `popularity`, `movieCount` |
54+
| `cast` | `cast`, `people` | `castId`, `castName`, `castImage`, `gender`, `popularity`, `movieCount` |
55+
| `yearGenreMovies` | `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
56+
| `directedMovies` | `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
57+
| `appearedMovies` | `cast`, `movies`, `genres` | `movieId`, `movieName`, `movieImage`, `year`, `rating`, `genreId`, `genreName` |
58+
59+
The final three queries are interesting since they are parameterized.
60+
`yearGenreMovies` query accepts optional `year` and `genreId` params to filter
61+
movies dynamically. Similarly, the `directedMovies` and `appearedMovies` queries
62+
accept a `personId` param. This means we can set up these queries once, but
63+
update them with different params to get different results as the user drills
64+
into different pages.
65+
66+
You might notice that many of these queries share the same selected Cell Ids.
67+
You'll discover that TinyBase lets you compose queries programmatically, so
68+
we'll be able to build these queries without much repetition: the common
7069
`queryMovieBasics` function is used to select the same Cell Ids into most of
7170
these query views.
7271

@@ -523,51 +522,56 @@ We also create query definitions for the other persistent queries. These use the
523522
`group` function to count the number of movies per year, genre, and so on, used
524523
in the overview components of each of the main sections of the app.
525524
526-
The `movies` query definition is re-used across the app, so note that it takes
527-
optional params for `year` and `genreId` so that different views can show movies
528-
by year or genre if required.
529-
530525
```js
531-
// ...
532-
queries.setQueryDefinition('years', 'movies', ({select, group}) => {
533-
select('year');
534-
select((_, rowId) => rowId).as('movieId');
535-
group('movieId', 'count').as('movieCount');
536-
});
526+
// ...
527+
queries.setQueryDefinition('years', 'movies', ({select, group}) => {
528+
select('year');
529+
select((_, rowId) => rowId).as('movieId');
530+
group('movieId', 'count').as('movieCount');
531+
});
537532

538-
queries.setQueryDefinition('genres', 'movies', ({select, join, group}) => {
539-
select('genreId');
540-
select((_, rowId) => rowId).as('movieId');
541-
join('genres', 'genreId');
542-
select('genres', 'name').as('genreName');
543-
group('movieId', 'count').as('movieCount');
544-
});
533+
queries.setQueryDefinition('genres', 'movies', ({select, join, group}) => {
534+
select('genreId');
535+
select((_, rowId) => rowId).as('movieId');
536+
join('genres', 'genreId');
537+
select('genres', 'name').as('genreName');
538+
group('movieId', 'count').as('movieCount');
539+
});
545540

546-
queries.setQueryDefinition('directors', 'movies', ({select, join, group}) => {
547-
select('directorId');
548-
select((_, rowId) => rowId).as('movieId');
549-
select('people', 'name').as('directorName');
550-
select('people', 'image').as('directorImage');
551-
select('people', 'gender');
552-
select('people', 'popularity');
553-
join('people', 'directorId');
554-
group('movieId', 'count').as('movieCount');
555-
});
541+
queries.setQueryDefinition('directors', 'movies', ({select, join, group}) => {
542+
select('directorId');
543+
select((_, rowId) => rowId).as('movieId');
544+
select('people', 'name').as('directorName');
545+
select('people', 'image').as('directorImage');
546+
select('people', 'gender');
547+
select('people', 'popularity');
548+
join('people', 'directorId');
549+
group('movieId', 'count').as('movieCount');
550+
});
556551

557-
queries.setQueryDefinition('cast', 'cast', ({select, join, group}) => {
558-
select('castId');
559-
select('movieId');
560-
select('people', 'name').as('castName');
561-
select('people', 'image').as('castImage');
562-
select('people', 'gender');
563-
select('people', 'popularity');
552+
queries.setQueryDefinition('cast', 'cast', ({select, join, group}) => {
553+
select('castId');
554+
select('movieId');
555+
select('people', 'name').as('castName');
556+
select('people', 'image').as('castImage');
557+
select('people', 'gender');
558+
select('people', 'popularity');
559+
join('people', 'castId');
560+
group('movieId', 'count').as('movieCount');
561+
});
562+
// ...
563+
```
564564
565-
join('people', 'castId');
566-
group('movieId', 'count').as('movieCount');
567-
});
565+
And finally the parameterized queries. The `yearGenreMovies` query definition is
566+
re-used across the app, so note that it takes optional params for `year` and
567+
`genreId` so that different views can show movies by year or genre if required.
568+
Similarly, the `directedMovies` and `appearedMovies` queries take a `personId`
569+
param to show movies for a specific director or actor.
568570
571+
```js
572+
// ...
569573
queries.setQueryDefinition(
570-
'movies',
574+
'yearGenreMovies',
571575
'movies',
572576
({select, join, where, param}) => {
573577
queryMovieBasics({select, join});
@@ -581,14 +585,59 @@ by year or genre if required.
581585
{year: null, genreId: null},
582586
);
583587

588+
queries.setQueryDefinition(
589+
'directedMovies',
590+
'movies',
591+
({select, join, where, param}) => {
592+
queryMovieBasics({select, join});
593+
where('directorId', param('personId'));
594+
},
595+
{personId: null},
596+
);
597+
598+
queries.setQueryDefinition(
599+
'appearedMovies',
600+
'cast',
601+
({select, join, where, param}) => {
602+
select('movieId');
603+
select('movies', 'name').as('movieName');
604+
select('movies', 'image').as('movieImage');
605+
select('movies', 'year');
606+
select('movies', 'rating');
607+
select('movies', 'genreId');
608+
select('genres', 'name').as('genreName');
609+
join('movies', 'movieId');
610+
join('genres', 'movies', 'genreId');
611+
where('castId', param('personId'));
612+
},
613+
{personId: null},
614+
);
615+
584616
return queries;
585617
}
586618
```
587619

588-
That's it for the main persistent queries that power most of the major views of
620+
That's it for the main persistent queries that power all the different views of
589621
the app. We'll refer to these by their query Id when we actually bind them to
590622
components.
591623

624+
Finally, let's create a convenient hook that will allow us to parameterize the
625+
`yearGenreMovies` query, and one to parameterize both the `directedMovies` and
626+
`appearedMovies` queries for a given person:
627+
628+
```js
629+
const useSetYearGenre = ({year, genreId}) =>
630+
useSetParamValuesCallback(
631+
'yearGenreMovies',
632+
(yearGenre) => yearGenre,
633+
)({year, genreId});
634+
635+
const useSetPersonMovies = (personId) => {
636+
useSetParamValuesCallback('directedMovies', (person) => person)({personId});
637+
useSetParamValuesCallback('appearedMovies', (person) => person)({personId});
638+
};
639+
```
640+
592641
## The ResultSortedTableInHtmlTable Component
593642

594643
Most of the movies app is built from tabular data views, and it's nice to have a
@@ -824,7 +873,7 @@ components to create the major views of the application.
824873

825874
First, the overview of all the rated movies in the database (which displays on
826875
the 'Movies' tab when the app first loads), comprising a
827-
`ResultSortedTableInHtmlTable` that renders the `movies` query with four
876+
ResultSortedTableInHtmlTable component that renders the `movies` query with four
828877
columns, sorted by rating.
829878

830879
```jsx
@@ -1010,19 +1059,18 @@ Moving on, the detail for a specific year is just a sorted table of the movies
10101059
from that year.
10111060

10121061
Here is a case where we can use a parameterized query, introduced in TinyBase
1013-
v7.2. The `movies` query was set up once in the app initialization, so we use
1014-
the `useSetParamValuesCallback` hook to update its parameters whenever the
1062+
v7.2. The `yearGenreMovies` query was set up once in the app initialization, so
1063+
we use the useSetParamValuesCallback hook to update its params whenever the
10151064
`year` prop changes. This is more efficient and ergonomic than rebuilding the
10161065
query definition every time.
10171066

10181067
```jsx
10191068
const YearDetail = ({year}) => {
1020-
useSetParamValuesCallback('movies', () => ({year}), [year])();
1021-
1069+
useSetYearGenre({year});
10221070
return (
10231071
<Page title={`Movies from ${year}`}>
10241072
<ResultSortedTableInHtmlTable
1025-
queryId="movies"
1073+
queryId="yearGenreMovies"
10261074
customCells={customCellsForMoviesInYear}
10271075
cellId="rating"
10281076
descending={true}
@@ -1043,16 +1091,16 @@ const customCellsForMoviesInYear = {
10431091
```
10441092

10451093
The genre detail page is very similar, using the same parameterized query but
1046-
setting the `genreId` parameter instead:
1094+
setting the `genreId` param instead:
10471095

10481096
```jsx
10491097
const GenreDetail = ({genreId}) => {
1050-
useSetParamValuesCallback('movies', () => ({genreId}), [genreId])();
1098+
useSetYearGenre({genreId});
10511099
const name = useCell('genres', genreId, 'name');
10521100
return name == null ? null : (
10531101
<Page title={`${name} movies`}>
10541102
<ResultSortedTableInHtmlTable
1055-
queryId="movies"
1103+
queryId="yearGenreMovies"
10561104
customCells={customCellsForMoviesInGenre}
10571105
cellId="rating"
10581106
descending={true}
@@ -1072,59 +1120,24 @@ const customCellsForMoviesInGenre = {
10721120
};
10731121
```
10741122

1075-
Finally, we build the detail page for a person. We create two queries on the fly
1076-
here, one for those movies for which the person is the director, and one for
1077-
those in which they are cast.
1123+
Finally, we build the detail page for a person. Again, we use parameterized
1124+
queries to get the movies they have directed and the movies in which they have
1125+
appeared without having to re-define the whole query each time.
10781126

1079-
The latter is slightly more complex since it needs to use the many-to-many
1080-
`cast` Table as its root, from where it joins to the `movies` Table and `genres`
1081-
Table in turn. Nevertheless, the result Cell Ids are named to be consistent with
1082-
the other queries, so that we can use the same custom components to render each
1083-
part of the HTML table.
1084-
1085-
This component is also slightly more complex that the others because it is also
1127+
This component is slightly more complex than the others because it is also
10861128
rendering some parts of its content directly from the `people` Table (rather
10871129
than via a query) - hence the use of the basic `useCell` hook and `CellView`
10881130
component, for example.
10891131

10901132
```jsx
10911133
const PersonDetail = ({personId}) => {
1092-
const queries = useQueries();
1093-
useMemo(
1094-
() =>
1095-
queries
1096-
.setQueryDefinition(
1097-
'moviesWithDirector',
1098-
'movies',
1099-
({select, join, where}) => {
1100-
queryMovieBasics({select, join});
1101-
where('directorId', personId);
1102-
},
1103-
)
1104-
.setQueryDefinition(
1105-
'moviesWithCast',
1106-
'cast',
1107-
({select, join, where}) => {
1108-
select('movieId');
1109-
select('movies', 'name').as('movieName');
1110-
select('movies', 'image').as('movieImage');
1111-
select('movies', 'year');
1112-
select('movies', 'rating');
1113-
select('movies', 'genreId');
1114-
select('genres', 'name').as('genreName');
1115-
join('movies', 'movieId');
1116-
join('genres', 'movies', 'genreId');
1117-
where('castId', personId);
1118-
},
1119-
),
1120-
[personId],
1121-
);
1134+
useSetPersonMovies(personId);
11221135

11231136
const props = {tableId: 'people', rowId: personId};
11241137
const name = useCell('people', personId, 'name');
11251138
const died = useCell('people', personId, 'died');
1126-
const moviesWithDirector = useResultRowIds('moviesWithDirector');
1127-
const moviesWithCast = useResultRowIds('moviesWithCast');
1139+
const directedMovies = useResultRowIds('directedMovies');
1140+
const appearedMovies = useResultRowIds('appearedMovies');
11281141

11291142
return name == null ? null : (
11301143
<Page title={name}>
@@ -1149,11 +1162,11 @@ const PersonDetail = ({personId}) => {
11491162
<CellView {...props} cellId="biography" />
11501163
</p>
11511164

1152-
{moviesWithDirector.length == 0 ? null : (
1165+
{directedMovies.length == 0 ? null : (
11531166
<>
11541167
<h2>As director:</h2>
11551168
<ResultSortedTableInHtmlTable
1156-
queryId="moviesWithDirector"
1169+
queryId="directedMovies"
11571170
customCells={customCellsForMoviesWithPeople}
11581171
cellId="rating"
11591172
descending={true}
@@ -1165,11 +1178,11 @@ const PersonDetail = ({personId}) => {
11651178
</>
11661179
)}
11671180

1168-
{moviesWithCast.length == 0 ? null : (
1181+
{appearedMovies.length == 0 ? null : (
11691182
<>
11701183
<h2>As cast:</h2>
11711184
<ResultSortedTableInHtmlTable
1172-
queryId="moviesWithCast"
1185+
queryId="appearedMovies"
11731186
customCells={customCellsForMoviesWithPeople}
11741187
cellId="rating"
11751188
descending={true}

0 commit comments

Comments
 (0)