Skip to content

Commit

Permalink
channel search: instead of persisting search results, enhance them wi…
Browse files Browse the repository at this point in the history
…th the author for display
  • Loading branch information
latter-bolden committed Apr 5, 2024
1 parent fefee41 commit 1ce713a
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 15 deletions.
4 changes: 2 additions & 2 deletions apps/tlon-mobile/src/screens/ChannelSearchScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useChannelSearch } from '@tloncorp/shared/dist';
import * as db from '@tloncorp/shared/dist/db';
import { XStack, YStack } from '@tloncorp/ui';
// TODO: update references to dist
Expand All @@ -7,7 +8,6 @@ import { useCallback, useLayoutEffect, useState } from 'react';
import { Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

import useChatSearch from '../hooks/useChatSearch';
import type { HomeStackParamList } from '../types';

type ChannelSearchProps = NativeStackScreenProps<
Expand All @@ -23,7 +23,7 @@ export default function ChannelSearch({
const group = db.useGroupByChannel(channel.id);
const [query, setQuery] = useState('');
const { posts, loading, errored, hasMore, loadMore, searchedThroughDate } =
useChatSearch(channel.id, query);
useChannelSearch(channel.id, query);

// handle full screen view without bottom nav, resets on dismout
useLayoutEffect(() => {
Expand Down
11 changes: 1 addition & 10 deletions packages/shared/src/api/channelsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import bigInt from 'big-integer';
import { useMemo } from 'react';

import * as db from '../db';
import { persistScanPosts } from '../sync';
import type * as ub from '../urbit';
import { stringToTa } from '../urbit/utils';
import { toPostData } from './postsApi';
Expand Down Expand Up @@ -40,14 +39,6 @@ const searchChatChannel = async (params: {
.map((post) => toPostData(post.seal.id, params.channelId, post));
const cursor = response.last;

if (posts.length) {
try {
await persistScanPosts(params.channelId, posts);
} catch (e) {
console.error('api: writing search result posts failed', e);
}
}

return { posts, cursor };
};

Expand All @@ -65,7 +56,7 @@ export function useInfiniteChannelSearch(channelId: string, query: string) {
return response;
},
initialPageParam: '',
getNextPageParam: (lastPage, allPages) => {
getNextPageParam: (lastPage) => {
if (lastPage.cursor === null) return undefined;
return lastPage.cursor;
},
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export type PostWithRelations = Post & {
reactions: Reaction[] | null;
author: Contact;
};

export type PostInsertWithAuthor = PostInsert & {
author: Contact;
};

export type PostType = Post['type'];
export type PostFlags = Pick<
Post,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useChannelSearch';
40 changes: 40 additions & 0 deletions packages/shared/src/hooks/useChannelSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import _ from 'lodash';
import { useEffect } from 'react';

import * as api from '../api';
import { useAttachAuthorToPostInserts } from './util/useAttachAuthorToPostInserts';

const MIN_RESULT_LOAD_THRESHOLD = 20;

export function useChannelSearch(channelId: string, query: string) {
const {
results,
searchedThroughDate,
isLoading: apiLoading,
isError: apiError,
hasNextPage,
fetchNextPage,
} = api.useInfiniteChannelSearch(channelId, query);

const posts = useAttachAuthorToPostInserts(results);

// Makes sure we load enough results to fill the screen before relying on infinite scroll
useEffect(() => {
if (
results.length < MIN_RESULT_LOAD_THRESHOLD &&
hasNextPage &&
!apiLoading
) {
fetchNextPage();
}
}, [results, hasNextPage, apiLoading, fetchNextPage]);

return {
posts,
searchedThroughDate,
loading: apiLoading,
errored: apiError,
hasMore: hasNextPage,
loadMore: fetchNextPage,
};
}
66 changes: 66 additions & 0 deletions packages/shared/src/hooks/util/useAttachAuthorToPostInserts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import _ from 'lodash';
import { useEffect, useMemo, useState } from 'react';

import * as db from '../../db';

export function useAttachAuthorToPostInserts(posts: db.PostInsert[]) {
const [postsWithAuthor, setPostsWithAuthor] = useState<
db.PostInsertWithAuthor[]
>([]);
const [authorsCache, setAuthorsCache] = useState<Record<string, db.Contact>>(
{}
);
const missingAuthors = useMemo(
() =>
_.uniq(posts.map((post) => post.authorId)).filter(
(author) => !authorsCache[author]
),
[posts, authorsCache]
);
const appendedPostIds = useMemo(
() => postsWithAuthor.map((post) => post.id),
[postsWithAuthor]
);

const addAuthorToCache = (author: db.Contact) => {
if (!authorsCache[author.id]) {
setAuthorsCache((prev) => ({ ...prev, [author.id]: author }));
}
};

const getMissingAuthors = async (missingAuthors: string[]) => {
const contactPromises = missingAuthors.map((authorId) =>
db.getContact({ id: authorId })
);
const newContacts = await Promise.all(contactPromises);
newContacts.forEach((newContact) =>
newContact ? addAuthorToCache(newContact) : null
);
};

useEffect(() => {
if (missingAuthors.length > 0) {
getMissingAuthors(missingAuthors);
}
}, [posts, missingAuthors]);

useEffect(() => {
const maybeReset = posts.length === 0 && postsWithAuthor.length > 0;
const incomplete = postsWithAuthor.length < posts.length;
const shouldUpdate =
posts.filter(
(post) =>
authorsCache[post.authorId] && !appendedPostIds.includes(post.id)
).length > 0;

if ((incomplete && shouldUpdate) || maybeReset) {
setPostsWithAuthor(
posts
.filter((post) => authorsCache[post.authorId])
.map((post) => ({ ...post, author: authorsCache[post.authorId] }))
);
}
}, [posts, authorsCache, postsWithAuthor, missingAuthors, appendedPostIds]);

return postsWithAuthor;
}
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type {
} from './types/native';
export { parseActiveTab, trimFullPath } from './logic/navigation';
export * from './logic/utils';
export * from './hooks';
export * as sync from './sync';
export * as utils from './logic/utils';
export * from './debug';
2 changes: 1 addition & 1 deletion packages/shared/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/ui/src/components/ChannelSearch/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function SearchResults({
navigateToPost,
search,
}: {
posts: db.PostWithRelations[];
posts: db.PostInsertWithAuthor[];
navigateToPost: (post: db.PostWithRelations) => void;
search: SearchState;
}) {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/ChatMessage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ChatMessage = memo(
firstUnread,
unreadCount,
}: {
post: db.PostWithRelations;
post: db.PostWithRelations | db.PostInsertWithAuthor;
firstUnread?: string;
unreadCount?: number;
}) => {
Expand Down

0 comments on commit 1ce713a

Please sign in to comment.