Skip to content

Commit

Permalink
Merge pull request #2601 from daostack/feature/store-read-unread-inbo…
Browse files Browse the repository at this point in the history
…x-states

Store read\unread states to improve performance
  • Loading branch information
budnik9 authored Mar 4, 2024
2 parents ca5b2d0 + 78117d6 commit 0152dba
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 28 deletions.
89 changes: 68 additions & 21 deletions src/shared/hooks/useCases/useInboxItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Logger, UserService } from "@/services";
import { addMetadataToItemsBatch } from "@/services/utils";
import {
checkIsFeedItemFollowLayoutItemWithFollowData,
FeedLayoutItemWithFollowData,
InboxItemsBatch as ItemsBatch,
} from "@/shared/interfaces";
import { InboxItem, Timestamp } from "@/shared/models";
Expand All @@ -21,6 +22,46 @@ interface Return
refetch: () => void;
}

const filterItemsInTheMiddle = (info: {
fetchedInboxItems: InboxItem[];
unread: boolean;
currentData: FeedLayoutItemWithFollowData[] | null;
}): { addedItems: InboxItem[]; removedItems: InboxItem[] } => {
const { fetchedInboxItems, unread, currentData } = info;
const newItems = currentData
? fetchedInboxItems.filter((fetchedItem) =>
currentData.every((item) =>
checkIsFeedItemFollowLayoutItemWithFollowData(item)
? item.feedItemFollowWithMetadata.id !== fetchedItem.itemId
: item.itemId !== fetchedItem.itemId,
),
)
: fetchedInboxItems;

if (!unread) {
return {
addedItems: newItems,
removedItems: [],
};
}

const removedItems = fetchedInboxItems.filter(
(fetchedItem) =>
!fetchedItem.unread &&
(currentData || []).some((item) =>
checkIsFeedItemFollowLayoutItemWithFollowData(item)
? item.feedItemFollowWithMetadata.id === fetchedItem.itemId
: item.itemId === fetchedItem.itemId,
),
);
const filteredItems = newItems.filter((item) => item.unread);

return {
addedItems: filteredItems,
removedItems,
};
};

export const useInboxItems = (
feedItemIdsForNotListening?: string[],
options?: { unread?: boolean },
Expand Down Expand Up @@ -76,6 +117,7 @@ export const useInboxItems = (
data,
firstDocTimestamp: startAt,
lastDocTimestamp: endAt,
unread,
} = inboxItems;

if (!userId || !startAt || !endAt) {
Expand All @@ -88,29 +130,34 @@ export const useInboxItems = (
endAt,
});

if (!isMounted || inboxItemsRef.current.unread) {
if (!isMounted) {
return;
}

const filteredItems = data
? fetchedInboxItems.filter((fetchedItem) =>
data.every((item) =>
checkIsFeedItemFollowLayoutItemWithFollowData(item)
? item.feedItemFollowWithMetadata.id !== fetchedItem.itemId
: item.itemId !== fetchedItem.itemId,
),
)
: fetchedInboxItems;

addNewInboxItems(
filteredItems.map((item) => ({
item,
statuses: {
isAdded: false,
isRemoved: false,
},
})),
);
const { addedItems, removedItems } = filterItemsInTheMiddle({
fetchedInboxItems,
unread,
currentData: data,
});
const addedItemsWithStatuses = addedItems.map((item) => ({
item,
statuses: {
isAdded: false,
isRemoved: false,
},
}));
const removedItemsWithStatuses = removedItems.map((item) => ({
item,
statuses: {
isAdded: false,
isRemoved: true,
},
}));

addNewInboxItems([
...addedItemsWithStatuses,
...removedItemsWithStatuses,
]);
} catch (err) {
Logger.error(err);
}
Expand All @@ -119,7 +166,7 @@ export const useInboxItems = (
return () => {
isMounted = false;
};
}, []);
}, [inboxItems.unread]);

useEffect(() => {
if (!inboxItems.firstDocTimestamp || !userId) {
Expand Down
5 changes: 5 additions & 0 deletions src/store/states/inbox/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const getInboxItems = createAsyncAction(
{
limit?: number;
unread?: boolean;
shouldUseLastStateIfExists?: boolean;
},
Omit<InboxItems, "loading" | "batchNumber">,
Error,
Expand Down Expand Up @@ -114,3 +115,7 @@ export const addChatChannelItem = createStandardAction(
export const removeEmptyChatChannelItems = createStandardAction(
InboxActionType.REMOVE_EMPTY_CHAT_CHANNEL_ITEMS,
)<string | void>();

export const saveLastState = createStandardAction(
InboxActionType.SAVE_LAST_STATE,
)<{ shouldSaveAsReadState: boolean }>();
2 changes: 2 additions & 0 deletions src/store/states/inbox/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ export enum InboxActionType {

ADD_CHAT_CHANNEL_ITEM = "@INBOX/ADD_CHAT_CHANNEL_ITEM",
REMOVE_EMPTY_CHAT_CHANNEL_ITEMS = "@INBOX/REMOVE_EMPTY_CHAT_CHANNEL_ITEMS",

SAVE_LAST_STATE = "@INBOX/SAVE_LAST_STATE",
}
60 changes: 54 additions & 6 deletions src/store/states/inbox/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import produce from "immer";
import { WritableDraft } from "immer/dist/types/types-external";
import { pick } from "lodash";
import { ActionType, createReducer } from "typesafe-actions";
import { InboxItemType, QueryParamKey } from "@/shared/constants";
import {
Expand All @@ -11,7 +12,7 @@ import { ChatChannel, CommonFeed, Timestamp } from "@/shared/models";
import { areTimestampsEqual } from "@/shared/utils";
import { getQueryParam } from "@/shared/utils/queryParams";
import * as actions from "./actions";
import { InboxItems, InboxSearchState, InboxState } from "./types";
import { InboxItems, InboxSearchState, InboxState, LastState } from "./types";
import { getFeedLayoutItemDateForSorting } from "./utils";

type Action = ActionType<typeof actions>;
Expand Down Expand Up @@ -39,6 +40,8 @@ export const INITIAL_INBOX_STATE: InboxState = {
sharedItem: null,
chatChannelItems: [],
nextChatChannelItemId: null,
lastReadState: null,
lastUnreadState: null,
};

const sortInboxItems = (data: FeedLayoutItemWithFollowData[]): void => {
Expand Down Expand Up @@ -446,12 +449,26 @@ export const reducer = createReducer<InboxState, Action>(INITIAL_INBOX_STATE)

return { ...INITIAL_INBOX_STATE };
})
.handleAction(actions.getInboxItems.request, (state) =>
.handleAction(actions.getInboxItems.request, (state, { payload }) =>
produce(state, (nextState) => {
nextState.items = {
...nextState.items,
loading: true,
};
const { unread = false, shouldUseLastStateIfExists = false } = payload;
const lastState = unread
? nextState.lastUnreadState
: nextState.lastReadState;

if (!shouldUseLastStateIfExists || !lastState) {
nextState.items = {
...nextState.items,
loading: true,
};
return;
}

nextState.items = lastState.items;
nextState.sharedFeedItemId = lastState.sharedFeedItemId;
nextState.sharedItem = lastState.sharedItem;
nextState.chatChannelItems = lastState.chatChannelItems;
nextState.nextChatChannelItemId = lastState.nextChatChannelItemId;
}),
)
.handleAction(actions.getInboxItems.success, (state, { payload }) =>
Expand Down Expand Up @@ -727,4 +744,35 @@ export const reducer = createReducer<InboxState, Action>(INITIAL_INBOX_STATE)
);
}
}),
)
.handleAction(actions.saveLastState, (state, { payload }) =>
produce(state, (nextState) => {
const { shouldSaveAsReadState } = payload;
const stateToSave: LastState = pick(nextState, [
"items",
"sharedFeedItemId",
"sharedItem",
"chatChannelItems",
"nextChatChannelItemId",
]);
const data = stateToSave.items.data || [];
stateToSave.items = {
...stateToSave.items,
loading: false,
hasMore: true,
firstDocTimestamp:
(data[0] && getFeedLayoutItemDateForSorting(data[0])) || null,
lastDocTimestamp:
(data[data.length - 1] &&
getFeedLayoutItemDateForSorting(data[data.length - 1])) ||
null,
batchNumber: data.length > 15 ? stateToSave.items.batchNumber : 2,
};

if (shouldSaveAsReadState) {
nextState.lastReadState = stateToSave;
} else {
nextState.lastUnreadState = stateToSave;
}
}),
);
7 changes: 6 additions & 1 deletion src/store/states/inbox/saga/getInboxItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function* getInboxItems(
action: ReturnType<typeof actions.getInboxItems.request>,
) {
const {
payload: { limit, unread = false },
payload: { limit, unread = false, shouldUseLastStateIfExists = false },
} = action;

try {
Expand All @@ -42,6 +42,11 @@ export function* getInboxItems(

const currentItems = (yield select(selectInboxItems)) as InboxItems;
const isFirstRequest = !currentItems.lastDocTimestamp;

if (shouldUseLastStateIfExists && !isFirstRequest) {
return;
}

const { data, firstDocTimestamp, lastDocTimestamp, hasMore } = (yield call(
UserService.getInboxItemsWithMetadata,
{
Expand Down
6 changes: 6 additions & 0 deletions src/store/states/inbox/saga/refetchInboxItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ export function* refetchInboxItems(
yield put(actions.resetSearchInboxItems());
}

yield put(
actions.saveLastState({
shouldSaveAsReadState: unread,
}),
);
yield put(actions.resetInboxItems());
yield put(
actions.getInboxItems.request({
limit: 15,
unread,
shouldUseLastStateIfExists: true,
}),
);
}
11 changes: 11 additions & 0 deletions src/store/states/inbox/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ export interface InboxItems {
unread: boolean;
}

export type LastState = Pick<
InboxState,
| "items"
| "sharedFeedItemId"
| "sharedItem"
| "chatChannelItems"
| "nextChatChannelItemId"
>;

export interface InboxState {
items: InboxItems;
sharedFeedItemId: string | null;
sharedItem: FeedLayoutItemWithFollowData | null;
chatChannelItems: ChatChannelLayoutItem[];
nextChatChannelItemId: string | null;
searchState: InboxSearchState;
lastReadState: LastState | null;
lastUnreadState: LastState | null;
}
4 changes: 4 additions & 0 deletions src/store/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const inboxTransform = createTransform(
if (inboundState.items.unread) {
return {
...inboundState,
lastReadState: null,
lastUnreadState: null,
items: { ...INITIAL_INBOX_ITEMS },
};
}
Expand All @@ -42,6 +44,8 @@ export const inboxTransform = createTransform(

return {
...inboundState,
lastReadState: null,
lastUnreadState: null,
items: {
...inboundState.items,
data,
Expand Down

0 comments on commit 0152dba

Please sign in to comment.