Skip to content

Commit

Permalink
fix: load letters all at once (not in batches)
Browse files Browse the repository at this point in the history
Fixes #12 (temporarily; we'll want to add a nice loading GIF or
something to ensure that the user knows something is going on).
  • Loading branch information
nicholaschiang committed May 4, 2021
1 parent 0c7ecc8 commit 95a6618
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 44 deletions.
58 changes: 22 additions & 36 deletions components/letters.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mutate, useSWRInfinite } from 'swr';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useSWR, { mutate } from 'swr';
import Head from 'next/head';
import NProgress from 'nprogress';
import Router from 'next/router';
Expand All @@ -12,7 +12,7 @@ import Button from 'components/button';
import Empty from 'components/empty';

import { Filter, User } from 'lib/model/user';
import { Letter, LetterJSON } from 'lib/model/letter';
import { Letter } from 'lib/model/letter';
import clone from 'lib/utils/clone';
import { fetcher } from 'lib/fetch';
import { period } from 'lib/utils';
Expand Down Expand Up @@ -134,37 +134,14 @@ export default function Letters() {
if (error) setLoading(false);
}, [error]);

const getKey = useCallback((pageIdx: number, prev: LettersRes | null) => {
if (!prev || pageIdx === 0) return '/api/letters';
return `/api/letters?pageToken=${prev.nextPageToken}`;
}, []);

const { data, setSize } = useSWRInfinite<LettersRes>(getKey);
const { data: letters } = useSWR<LettersRes>('/api/letters');
const { user } = useUser();

const letters = useMemo(() => {
const noDuplicates: LetterJSON[] = [];
data?.forEach((d) => {
d.letters.forEach((l) => {
if (!noDuplicates.some((n) => n.from === l.from)) noDuplicates.push(l);
});
});
return noDuplicates;
}, [data]);

useEffect(() => {
if (!data) return;
void setSize((prev) => (prev >= 50 ? prev : prev + 1));
}, [data, setSize]);
useEffect(() => {
setSelected(new Set(user.filter.senders));
}, [user.filter.senders]);

const onSave = useCallback(async () => {
setLoading(true);
try {
const selectedLetters = letters.filter((l) => selected.has(l.from));
if (!selectedLetters || selectedLetters.length === 0) return;
const selectedLetters = letters?.filter((l) => selected.has(l.from));
if (!selectedLetters?.length) return;

const filter: Filter = { id: user.filter.id, senders: [] };
selectedLetters.forEach((l) => {
Expand All @@ -180,11 +157,12 @@ export default function Letters() {
}
}, [selected, letters, user]);

const other = useMemo(() => letters.filter((l) => l.category === 'other'), [
letters,
]);
const other = useMemo(
() => (letters || []).filter((letter) => letter.category === 'other'),
[letters]
);
const important = useMemo(
() => letters.filter((l) => l.category === 'important'),
() => (letters || []).filter((letter) => letter.category === 'important'),
[letters]
);
const loadingList = useMemo(
Expand All @@ -195,6 +173,14 @@ export default function Letters() {
[]
);

// Select all the saved newsletters (that the user has previously selected).
// TODO: Ensure that all of these show up in the newsletter list so that users
// can unselect them as needed (i.e. even if they aren't in the last 5000
// messages in their Gmail inbox).
useEffect(() => {
setSelected(new Set(user.filter.senders));
}, [user.filter.senders]);

// Pre-select all of the "important" newsletters. From @martinsrna:
// > The reason is that in the whitelist "database" we created (that decides
// > if a newsletter is important or not), there are mostly substacks and more
Expand Down Expand Up @@ -239,8 +225,8 @@ export default function Letters() {
))}
</ul>
)}
{!data && <ul>{loadingList}</ul>}
{data && !important.length && <Empty>No newsletters found</Empty>}
{!letters && <ul>{loadingList}</ul>}
{letters && !important.length && <Empty>No newsletters found</Empty>}
<h2>Other subscriptions, including promotions</h2>
{!!other.length && (
<ul>
Expand All @@ -262,8 +248,8 @@ export default function Letters() {
))}
</ul>
)}
{!data && <ul>{loadingList}</ul>}
{data && !other.length && <Empty>No subscriptions found</Empty>}
{!letters && <ul>{loadingList}</ul>}
{letters && !other.length && <Empty>No subscriptions found</Empty>}
<Button disabled={loading} onClick={onSave}>
Go to your feed
</Button>
Expand Down
2 changes: 1 addition & 1 deletion lib/api/get/letters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default async function getLetters(
logger.verbose(`Fetching letters for ${user}...`);
const client = gmail(user.token);
const { data } = await client.users.messages.list({
maxResults: 50,
maxResults: 5000,
userId: 'me',
pageToken,
});
Expand Down
11 changes: 4 additions & 7 deletions pages/api/letters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { LetterJSON } from 'lib/model/letter';
import getLetters from 'lib/api/get/letters';
import getUser from 'lib/api/get/user';
import { handle } from 'lib/api/error';
import verifyAuth from 'lib/api/verify/auth';
import logger from 'lib/api/logger';
import verifyAuth from 'lib/api/verify/auth';

export type LettersRes = { letters: LetterJSON[]; nextPageToken: string };
export type LettersRes = LetterJSON[];

/**
* GET - Lists the letters for the given user.
Expand All @@ -27,11 +27,8 @@ export default async function letters(
const { pageToken } = req.query as { pageToken?: string };
const { uid } = await verifyAuth(req.headers);
const user = await getUser(uid);
const { nextPageToken, letters } = await getLetters(user, pageToken);
res.status(200).json({
nextPageToken,
letters: letters.map((l) => l.toJSON()),
});
const { letters: lettersData } = await getLetters(user, pageToken);
res.status(200).json(lettersData.map((l) => l.toJSON()));
logger.info(`Fetched ${letters.length} letters for ${user}.`);
} catch (e) {
handle(e, res);
Expand Down

0 comments on commit 95a6618

Please sign in to comment.