Skip to content

Commit 731d153

Browse files
committed
[React+django] add "quickSearch" & "booksByGenre" to the list of PostgREST calls replaced with GraphQL ones
Also circumvent the bug we face with Graphene Python Enums (@link graphql-python/graphql-core#140)
1 parent b4a9288 commit 731d153

File tree

5 files changed

+172
-85
lines changed

5 files changed

+172
-85
lines changed

client/src/hoc/Book/BookFullContainer.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface BookFullContainerProps {
1515
hocToolkit: HigherOrderComponentToolkit;
1616
}
1717

18-
interface BookFullContainerState {
18+
interface BookFullContainerReadyState {
1919
loading: false;
2020
bookFull: BookFull;
2121
}
@@ -24,23 +24,32 @@ interface BookFullContainerLoadingState {
2424
loading: true;
2525
}
2626

27+
type BookFullContainerPossibleState = BookFullContainerReadyState | BookFullContainerLoadingState;
28+
2729
export class BookFullContainer extends React.Component<
2830
BookFullContainerProps,
29-
BookFullContainerState | BookFullContainerLoadingState
31+
BookFullContainerPossibleState
3032
> {
33+
public mounted: boolean = false;
34+
3135
constructor(props: BookFullContainerProps) {
3236
super(props);
3337
this.state = this.getDerivedStateFromPropsAndAppState();
3438
this.onBookDataFetched = this.onBookDataFetched.bind(this);
3539
}
3640

3741
public render() {
38-
if (this.state.loading) {
42+
const loading: boolean = this.state.loading || this.props.bookId !== this.state.bookFull.id;
43+
if (loading) {
44+
// We either don't have any book in our state yet,
45+
// or that's not the one we want to render (likely because the user navigated to another book).
46+
// --> let's fetch the wanted book data!
3947
this.fetchData();
4048
return <div className="loading">Loading full book...</div>;
4149
}
4250

43-
const bookFull: BookFull = this.state.bookFull;
51+
const state = this.state as BookFullContainerReadyState;
52+
const bookFull: BookFull = state.bookFull;
4453
const appState = this.props.hocToolkit.appStateStore.getState();
4554

4655
const genresWithStats = this.getSortedGenresWithStats(
@@ -61,12 +70,25 @@ export class BookFullContainer extends React.Component<
6170
);
6271
}
6372

73+
public componentWillMount() {
74+
this.mounted = true;
75+
}
76+
77+
public componentWillUnmount() {
78+
this.mounted = false;
79+
}
80+
6481
private fetchData(): void {
6582
this.props.hocToolkit.messageBus.on(EVENTS.BOOK_DATA_FETCHED, this.onBookDataFetched);
6683
this.props.hocToolkit.actionsDispatcher.fetchBookWithGenreStats(this.props.bookId);
6784
}
6885

6986
private onBookDataFetched(): void {
87+
if (!this.mounted) {
88+
this.props.hocToolkit.messageBus.off(EVENTS.BOOK_DATA_FETCHED, this.onBookDataFetched);
89+
return;
90+
}
91+
7092
const newState = this.getDerivedStateFromPropsAndAppState();
7193
if (!newState.loading) {
7294
// We now have our full book data!
@@ -76,9 +98,7 @@ export class BookFullContainer extends React.Component<
7698
}
7799
}
78100

79-
private getDerivedStateFromPropsAndAppState():
80-
| BookFullContainerState
81-
| BookFullContainerLoadingState {
101+
private getDerivedStateFromPropsAndAppState(): BookFullContainerPossibleState {
82102
const appState = this.props.hocToolkit.appStateStore.getState();
83103
const book = appState.booksById[this.props.bookId];
84104

client/src/repositories/BooksGraphqlRepository.ts

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const quickSearchResultsCache: { [cacheKey: string]: QuickSearchResult[] } = {};
2020

2121
/**
2222
* This module gets a bit messy, we'll probably refactor it at some point :-)
23+
*
24+
* N.B.: I haven't used a specific GraphQL client library like Apollo, because I always send the same static queries and
25+
* I don't need to add the size of such a client lib to my JS.
2326
*/
2427
export class BooksGraphqlRepository implements BooksRepository {
2528
public async getFeaturedBooks(lang: Lang): Promise<BooksById> {
@@ -36,7 +39,7 @@ query {
3639
slug
3740
coverPath
3841
genres
39-
42+
4043
author {
4144
authorId
4245
firstName
@@ -47,8 +50,8 @@ query {
4750
nbBooks
4851
}
4952
}
50-
51-
}
53+
54+
}
5255
`;
5356
const response = await this.requestGraphql(graphqlQuery, null);
5457
const featuredBooks: Book[] = response.data.data.featuredBooks.map(mapBookFromServer);
@@ -71,7 +74,7 @@ query bookById($bookId: BookId!) {
7174
genres
7275
epubSize
7376
mobiSize
74-
77+
7578
author {
7679
authorId
7780
firstName
@@ -82,18 +85,18 @@ query bookById($bookId: BookId!) {
8285
nbBooks
8386
}
8487
}
85-
88+
8689
genresStats {
8790
title
8891
nbBooks
89-
92+
9093
nbBooksByLang {
9194
lang
9295
nbBooks
9396
}
9497
}
9598
}
96-
99+
97100
}
98101
99102
`;
@@ -112,11 +115,27 @@ query bookById($bookId: BookId!) {
112115
return Promise.resolve(cacheForThisPatternAndLang);
113116
}
114117

115-
const response = await axios.get("/rpc/quick_autocompletion", {
116-
params: { pattern, lang },
117-
});
118+
const graphqlQuery = `
119+
query quickSearch($pattern: String!, $lang: String) {
120+
121+
quickSearch(search: $pattern, lang: $lang) {
122+
type
123+
bookId
124+
bookLang
125+
bookTitle
126+
bookSlug
127+
authorId
128+
authorFirstName
129+
authorLastName
130+
authorSlug
131+
authorNbBooks
132+
highlight
133+
}
118134
119-
const matchingBooks = response.data.map(mapQuickAutocompletionDataFromServer);
135+
}
136+
`;
137+
const response = await this.requestGraphql(graphqlQuery, { pattern, lang });
138+
const matchingBooks = response.data.data.quickSearch.map(mapQuickSearchDataFromServer);
120139
quickSearchResultsCache[cacheKey] = matchingBooks;
121140

122141
return Promise.resolve(matchingBooks);
@@ -127,19 +146,53 @@ query bookById($bookId: BookId!) {
127146
lang: Lang,
128147
pagination: PaginationRequestData
129148
): Promise<PaginatedBooksList> {
130-
const response = await axios.get("/rpc/get_books_by_genre", {
131-
params: {
132-
genre,
133-
lang,
134-
page: pagination.page,
135-
nb_per_page: pagination.nbPerPage,
136-
},
149+
const graphqlQuery = `
150+
query booksByGenre($genre: String!, $lang: String, $page: Int, $nbPerPage: Int) {
151+
152+
booksByGenre(genre: $genre, lang: $lang, page: $page, nbPerPage: $nbPerPage) {
153+
154+
books {
155+
bookId
156+
lang
157+
title
158+
subtitle
159+
slug
160+
coverPath
161+
genres
162+
163+
author {
164+
authorId
165+
firstName
166+
lastName
167+
birthYear
168+
deathYear
169+
slug
170+
nbBooks
171+
}
172+
}
173+
174+
meta {
175+
page
176+
nbPerPage
177+
nbResults
178+
nbResultsForAllLangs
179+
}
180+
181+
}
182+
183+
}
184+
`;
185+
const response = await this.requestGraphql(graphqlQuery, {
186+
genre,
187+
lang,
188+
page: pagination.page,
189+
nbPerPage: pagination.nbPerPage,
137190
});
138191

139192
const booksWithPagination: ServerResponse.BooksDataWithPagination<ServerResponse.BookData> =
140-
response.data[0];
193+
response.data.data.booksByGenre;
141194
const paginationData: PaginationResponseData = getPaginationResponseDataFromServerResponse(
142-
booksWithPagination.pagination
195+
booksWithPagination.meta
143196
);
144197
const booksForThisGenre = getBooksByIdFromBooksArray(
145198
(booksWithPagination.books || []).map(mapBookFromServer)
@@ -166,9 +219,9 @@ query bookById($bookId: BookId!) {
166219
});
167220

168221
const booksWithPagination: ServerResponse.BooksDataWithPagination<ServerResponse.BookData> =
169-
response.data[0];
222+
response.data.data.booksByAuthor;
170223
const paginationData: PaginationResponseData = getPaginationResponseDataFromServerResponse(
171-
booksWithPagination.pagination
224+
booksWithPagination.meta
172225
);
173226
const booksForThisGenre = getBooksByIdFromBooksArray(
174227
(booksWithPagination.books || []).map(mapBookFromServer)
@@ -187,7 +240,7 @@ query bookIntro($bookId: BookId!) {
187240
book(bookId: $bookId) {
188241
intro
189242
}
190-
243+
191244
}
192245
193246
@@ -256,51 +309,49 @@ function mapBookWithGenreStatsFromServer(
256309
}
257310

258311
function mapGenreWithStatsFromServer(row: ServerResponse.GenreWithStats): GenreWithStats {
259-
let nbBooksByLang: { [lang: string]: number } = {};
312+
const nbBooksByLang: { [lang: string]: number } = {};
260313
for (const nbBooksForLang of row.nbBooksByLang) {
261314
nbBooksByLang[nbBooksForLang.lang] = nbBooksForLang.nbBooks;
262315
}
263316

264317
return {
265318
title: row.title,
266319
nbBooks: row.nbBooks,
267-
nbBooksByLang: nbBooksByLang,
320+
nbBooksByLang,
268321
};
269322
}
270323

271-
function mapQuickAutocompletionDataFromServer(
272-
row: ServerResponse.QuickAutocompletionData
273-
): QuickSearchResult {
324+
function mapQuickSearchDataFromServer(row: ServerResponse.QuickSearchData): QuickSearchResult {
274325
const rowType = row.type;
275326
return {
276327
resultType: rowType,
277328
book:
278329
"book" === rowType
279330
? {
280-
id: row.book_id as string,
281-
title: row.book_title as string,
282-
lang: row.book_lang as string,
283-
slug: row.book_slug as string,
331+
id: row.bookId as string,
332+
title: row.bookTitle as string,
333+
lang: row.bookLang as string,
334+
slug: row.bookSlug as string,
284335
}
285336
: null,
286337
author: {
287-
id: row.author_id,
288-
firstName: row.author_first_name,
289-
lastName: row.author_last_name,
290-
slug: row.author_slug,
291-
nbBooks: row.author_nb_books,
338+
id: row.authorId,
339+
firstName: row.authorFirstName,
340+
lastName: row.authorLastName,
341+
slug: row.authorSlug,
342+
nbBooks: row.authorNbBooks,
292343
},
293344
highlight: row.highlight,
294345
};
295346
}
296347

297348
function getPaginationResponseDataFromServerResponse(
298-
responsePagination: ServerResponse.PaginationResponseData
349+
responsePagination: ServerResponse.PaginationMetaData
299350
): PaginationResponseData {
300351
return {
301352
page: responsePagination.page,
302-
nbPerPage: responsePagination.nb_per_page,
303-
nbResultsTotal: responsePagination.nb_results_total,
304-
nbResultsTotalForAllLangs: responsePagination.nb_results_total_for_all_langs,
353+
nbPerPage: responsePagination.nbPerPage,
354+
nbResultsTotal: responsePagination.nbResults,
355+
nbResultsTotalForAllLangs: responsePagination.nbResultsForAllLangs,
305356
};
306357
}

client/src/repositories/graphql-server-responses.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ export interface BookWithGenreStats {
3030
genresStats: GenreWithStats[];
3131
}
3232

33-
export interface QuickAutocompletionData {
33+
export interface QuickSearchData {
3434
type: "book" | "author";
35-
book_id: string | null;
36-
book_title: string | null;
37-
book_lang: string;
38-
book_slug: string;
39-
author_id: string;
40-
author_first_name: string;
41-
author_last_name: string;
42-
author_slug: string;
43-
author_nb_books: number;
35+
bookId: string | null;
36+
bookTitle: string | null;
37+
bookLang: string;
38+
bookSlug: string;
39+
authorId: string;
40+
authorFirstName: string;
41+
authorLastName: string;
42+
authorSlug: string;
43+
authorNbBooks: number;
4444
highlight: number;
4545
}
4646

@@ -57,12 +57,12 @@ export interface NbBooksByLang {
5757

5858
export interface BooksDataWithPagination<T> {
5959
books: T[];
60-
pagination: PaginationResponseData;
60+
meta: PaginationMetaData;
6161
}
6262

63-
export interface PaginationResponseData {
63+
export interface PaginationMetaData {
6464
page: number;
65-
nb_per_page: number;
66-
nb_results_total: number;
67-
nb_results_total_for_all_langs: number;
65+
nbPerPage: number;
66+
nbResults: number;
67+
nbResultsForAllLangs: number;
6868
}

0 commit comments

Comments
 (0)