Skip to content

Commit

Permalink
Merge pull request #109 from jamesgiu/QH-108
Browse files Browse the repository at this point in the history
QH-108 Paginated game feed + remove exhaustive deps rule
  • Loading branch information
jamesgiu authored Sep 4, 2021
2 parents 1e55bca + 777c9ea commit c9322d6
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
],
"rules": {
"@typescript-eslint/no-explicit-any": 2,
"@typescript-eslint/explicit-function-return-type": 2
"@typescript-eslint/explicit-function-return-type": 2,
"react-hooks/exhaustive-deps": 0
}
}
6 changes: 3 additions & 3 deletions src/components/QHDataLoader/QHDataLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ function QHDataLoader(props: QHDataLoaderProps): JSX.Element {
if (intervalRef.current) {
props.setLoading(false);
}
}, [props.players, props.matches, props.badges, props.happyHour]); // eslint-disable-line react-hooks/exhaustive-deps
}, [props.players, props.matches, props.badges, props.happyHour]);

// On component mount.
useEffect(() => {
getData();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
}, []);

// Set the data loop, and on prop change, reset the loop as the Interval function will retain the props
// present at the time of invocation.
Expand All @@ -150,7 +150,7 @@ function QHDataLoader(props: QHDataLoaderProps): JSX.Element {
getData();
props.setForceRefresh(false);
}
}, [props, polling]); // eslint-disable-line react-hooks/exhaustive-deps
}, [props, polling]);

return (
<div className={"data-loader"}>
Expand Down
10 changes: 10 additions & 0 deletions src/components/RecentGames/RecentGames.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@
font-weight: 500;
color: orange;
}

.ui.pagination.menu a {
background-color: rgba(53, 54, 58, 1);
color: white;
}

.ui.pagination.menu .active.item,
.ui.pagination.menu a:hover {
background-color: rgb(196, 186, 183);
}
95 changes: 76 additions & 19 deletions src/components/RecentGames/RecentGames.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import React, { useEffect } from "react";
import "./RecentGames.css";
import { Divider, Feed, Header, Icon, Transition } from "semantic-ui-react";
import { Divider, Feed, Header, Icon, Pagination, PaginationProps, Transition } from "semantic-ui-react";
import { FeedEventProps } from "semantic-ui-react/dist/commonjs/views/Feed/FeedEvent";
import ReactTimeAgo from "react-time-ago";
import { TTDataPropsTypeCombined } from "../../containers/shared";
import { getPlayersMap } from "../QHDataLoader/QHDataLoader";
import { DbMatch } from "../../types/database/models";

export interface RecentGamesProps {
focusedPlayerId?: string;
Expand All @@ -13,33 +14,67 @@ export interface RecentGamesProps {
export type RecentGamesCombinedProps = RecentGamesProps & TTDataPropsTypeCombined;

function RecentGames(props: RecentGamesCombinedProps): JSX.Element {
const PAGE_SIZE = 5;
// Track this in state, as we may potentially filter the matches to only be focused ones.
const [matches, setMatches] = React.useState<DbMatch[]>(props.matches);
const [currentPage, setCurrentPage] = React.useState<number>(0);

// Runs on mount.
useEffect(() => {
sortAndFilterMatches();
}, []);

// When the matches change, re-sort them.
useEffect(() => {
sortAndFilterMatches();
}, [props.matches]);

const sortAndFilterMatches = (): void => {
let sortedAndFilteredMatches: DbMatch[] = props.matches;
// Determine which matches will make up our Recent Games set on mount to save performance.
// If we are focusing on a player, do the filter now.
if (props.focusedPlayerId) {
sortedAndFilteredMatches = sortedAndFilteredMatches.filter(
(match) =>
match.winning_player_id === props.focusedPlayerId ||
match.losing_player_id === props.focusedPlayerId
);
}

// Sort list from newest to oldest
sortedAndFilteredMatches = sortedAndFilteredMatches.sort((matchA, matchB) => {
return new Date(matchB.date).getTime() - new Date(matchA.date).getTime();
});

// Set this filtered and sorted match object in state, for later use.
setMatches(sortedAndFilteredMatches);
};

const getMatchEvents = (): FeedEventProps[] => {
if (props.players.length === 0) {
if (matches.length === 0 || props.players.length === 0) {
return [];
}

const events: FeedEventProps[] = [];
const playersMap = getPlayersMap(props.players);

// Sort list from newest to oldest
props.matches.sort((matchA, matchB) => {
return new Date(matchB.date).getTime() - new Date(matchA.date).getTime();
});
// Use index access for pagination, with a max result size of PAGE_SIZE and offset generated by the current page
// * page-size.
const offset = currentPage * PAGE_SIZE;
const nextPageOffset = (currentPage + 1) * PAGE_SIZE;

for (let i = offset; i < nextPageOffset; i++) {
if (i > matches.length - 1) {
break;
}

const match = matches[i];

props.matches.forEach((match) => {
const winningPlayer = playersMap.get(match.winning_player_id);
const losingPlayer = playersMap.get(match.losing_player_id);

if (!(winningPlayer && losingPlayer)) {
return;
}

// If we are focusing on a certain player's recent games only
if (props.focusedPlayerId) {
if (winningPlayer.id !== props.focusedPlayerId && losingPlayer.id !== props.focusedPlayerId) {
// Skip this match in the loop, as it does not contain our focused player.
return;
}
break;
}

events.push({
Expand Down Expand Up @@ -80,19 +115,41 @@ function RecentGames(props: RecentGamesCombinedProps): JSX.Element {
),
icon: winningPlayer.icon,
});
});
}

return events;
};

const getTotalPages = (): number => {
return Math.max(Math.floor(matches.length / PAGE_SIZE), 1);
};

const handlePageChange = (event: React.MouseEvent<HTMLAnchorElement>, data: PaginationProps): void => {
setCurrentPage(data.activePage as number);
};

return (
<div className="recent-games">
<Header as={"h2"} icon>
<Icon name="history" circular />
<Header.Content>Recent games</Header.Content>
</Header>
<Transition visible={!props.loading}>
<Feed className={"games-feed"} events={getMatchEvents()} />
<span>
<Feed className={"games-feed"} events={getMatchEvents()} />
{
/* Show pagination if number of pages is greater than 1 */
getTotalPages() > 1 && (
<Pagination
totalPages={getTotalPages()}
defaultActivePage={1}
onPageChange={handlePageChange}
firstItem={null}
lastItem={null}
/>
)
}
</span>
</Transition>
</div>
);
Expand Down

0 comments on commit c9322d6

Please sign in to comment.