Skip to content

Commit 2a3a36a

Browse files
authored
fix(InfiniteListItems): Fix types to support non-array based ApiResult objects (#97690)
Before we were hard-coding the assumption about the response shape: ``` # before const loadedRows = deduplicateItems( data ? data.pages.flatMap(result => result[0]) : [] ); ``` This was assuming that `result` is an ApiResult, which is a tuple. Now we have better types! These let us extract the list of things from inside the ApiResult first, it's just basically the do-it-yourself method, just implement: `(page: Response[]) => ListItem[];` Example So if you have some data like this: ``` const fromTheApi: ReplayListResponse = {data: [{replayId: 123}, {replayId: 234}]}; const response: ApiResult<ReplayListResponse> = [fromTheApi, status, rawResponse]; ``` it's possible to extract out the `[{replayId: 123}, {replayId: 234}]` from inside the ApiResult now with `deduplicateItems={pages => uniqBy(pages.flatMap(page => page[0].data), 'replayId')}`
1 parent 12b7cc8 commit 2a3a36a

File tree

2 files changed

+28
-12
lines changed

2 files changed

+28
-12
lines changed

static/app/components/feedback/list/feedbackList.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import uniqBy from 'lodash/uniqBy';
44

55
import waitingForEventImg from 'sentry-images/spot/waiting-for-event.svg';
66

7+
import type {ApiResult} from 'sentry/api';
78
import {Tooltip} from 'sentry/components/core/tooltip';
89
import ErrorBoundary from 'sentry/components/errorBoundary';
910
import FeedbackListHeader from 'sentry/components/feedback/list/feedbackListHeader';
@@ -57,8 +58,13 @@ export default function FeedbackList() {
5758
backgroundUpdatingMessage={() => null}
5859
loadingMessage={() => <LoadingIndicator />}
5960
>
60-
<InfiniteListItems<FeedbackIssueListItem>
61-
deduplicateItems={items => uniqBy(items, 'id')}
61+
<InfiniteListItems<FeedbackIssueListItem, ApiResult<FeedbackIssueListItem[]>>
62+
deduplicateItems={pages =>
63+
uniqBy(
64+
pages.flatMap(page => page[0]),
65+
'id'
66+
)
67+
}
6268
estimateSize={() => 24}
6369
queryResult={queryResult}
6470
itemRenderer={({item}) => (

static/app/components/infiniteList/infiniteListItems.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,53 @@ import {t} from 'sentry/locale';
99

1010
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
1111

12-
interface Props<Data> {
12+
/**
13+
* Types:
14+
* - ListItem represents the type of a single item in the list, after being extracted and de-duplicated from the response
15+
* - Response represents the type of the response from the API. For example: `ApiResult<ListItem[]>` or `ApiResult<{data: ListItem[]}>`
16+
*
17+
* `deduplicateItems` is a function to transform Response into an array of ListItem objects. For the most common cases:
18+
* - When `Response = ApiResult<ListItem[]>` then `deduplicateItems={pages => uniqBy(pages.flatMap(page => page[0]), 'id')}`
19+
* - When `Response = ApiResult<{data: ListItem[]}>` then `deduplicateItems={pages => uniqBy(pages.flatMap(page => page[0].data), 'id')}`
20+
*/
21+
interface Props<ListItem, Response = Array<ApiResult<ListItem[]>>> {
22+
deduplicateItems: (page: Response[]) => ListItem[];
1323
itemRenderer: ({
1424
item,
1525
virtualItem,
1626
}: {
17-
item: Data;
27+
item: ListItem;
1828
virtualItem: VirtualItem;
1929
}) => React.ReactNode;
2030
queryResult: Overwrite<
2131
Pick<
22-
UseInfiniteQueryResult<InfiniteData<ApiResult<Data[]>>, Error>,
32+
UseInfiniteQueryResult<InfiniteData<Response>, Error>,
2333
'data' | 'hasNextPage' | 'isFetchingNextPage' | 'fetchNextPage'
2434
>,
2535
{fetchNextPage: () => Promise<unknown>}
2636
>;
27-
deduplicateItems?: (items: Data[]) => Data[];
2837
emptyMessage?: () => React.ReactNode;
2938
estimateSize?: () => number;
3039
loadingCompleteMessage?: () => React.ReactNode;
3140
loadingMoreMessage?: () => React.ReactNode;
3241
overscan?: number;
3342
}
3443

35-
export default function InfiniteListItems<Data>({
36-
deduplicateItems = _ => _,
44+
export default function InfiniteListItems<
45+
ListItem,
46+
Response = Array<ApiResult<ListItem[]>>,
47+
>({
48+
deduplicateItems,
3749
emptyMessage = EmptyMessage,
3850
estimateSize,
3951
itemRenderer,
4052
loadingCompleteMessage = LoadingCompleteMessage,
4153
loadingMoreMessage = LoadingMoreMessage,
4254
overscan,
4355
queryResult,
44-
}: Props<Data>) {
56+
}: Props<ListItem, Response>) {
4557
const {data, hasNextPage, isFetchingNextPage, fetchNextPage} = queryResult;
46-
const loadedRows = deduplicateItems(
47-
data ? data.pages.flatMap(result => result[0]) : []
48-
);
58+
const loadedRows = deduplicateItems(data?.pages ?? []);
4959
const parentRef = useRef<HTMLDivElement>(null);
5060

5161
const rowVirtualizer = useVirtualizer({

0 commit comments

Comments
 (0)