Merge pull request #30 from jedmund/paginate-collections

Add infinite scrolling to collections
This commit is contained in:
Justin Edmund 2022-03-21 03:56:05 -07:00 committed by GitHub
commit 5a3c0fd408
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 206 additions and 107 deletions

View file

@ -5,8 +5,10 @@ import { useCookies } from 'react-cookie'
import { queryTypes, useQueryState } from 'next-usequerystate' import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from 'lodash.clonedeep'
import api from '~utils/api' import api from '~utils/api'
import { elements, allElement } from '~utils/Element' import { elements, allElement } from '~utils/Element'
@ -30,9 +32,7 @@ const ProfileRoute: React.FC = () => {
// Set up cookies // Set up cookies
const [cookies] = useCookies(['account']) const [cookies] = useCookies(['account'])
const headers = (cookies.account) ? { const headers = (cookies.account) ? {
headers: { 'Authorization': `Bearer ${cookies.account.access_token}`
'Authorization': `Bearer ${cookies.account.access_token}`
}
} : {} } : {}
// Set up router // Set up router
@ -54,6 +54,11 @@ const ProfileRoute: React.FC = () => {
const [raid, setRaid] = useState<Raid>() const [raid, setRaid] = useState<Raid>()
const [user, setUser] = useState<User>(emptyUser) const [user, setUser] = useState<User>(emptyUser)
// Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState("element", {
@ -100,17 +105,18 @@ const ProfileRoute: React.FC = () => {
} }
}, []) }, [])
const fetchProfile = useCallback(() => { const fetchProfile = useCallback(({ replace }: { replace: boolean }) => {
const filters = { const filters = {
params: { params: {
element: (element != -1) ? element : undefined, element: (element != -1) ? element : undefined,
raid: (raid) ? raid.id : undefined, raid: (raid) ? raid.id : undefined,
recency: (recency != -1) ? recency : undefined recency: (recency != -1) ? recency : undefined,
page: currentPage
} }
} }
if (username && !Array.isArray(username)) if (username && !Array.isArray(username))
api.endpoints.users.getOne({ id: username , params: {...filters, ...headers} }) api.endpoints.users.getOne({ id: username , params: {...filters, ...{ headers: headers }}})
.then(response => { .then(response => {
setUser({ setUser({
id: response.data.user.id, id: response.data.user.id,
@ -120,15 +126,32 @@ const ProfileRoute: React.FC = () => {
private: response.data.user.private private: response.data.user.private
}) })
const parties: Party[] = response.data.user.parties setTotalPages(response.data.parties.total_pages)
setParties(parties.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1)) setRecordCount(response.data.parties.count)
if (replace)
replaceResults(response.data.parties.count, response.data.parties.results)
else
appendResults(response.data.parties.results)
}) })
.then(() => { .then(() => {
setFound(true) setFound(true)
setLoading(false) setLoading(false)
}) })
.catch(error => handleError(error)) .catch(error => handleError(error))
}, [element, raid, recency]) }, [currentPage, parties, element, raid, recency])
function replaceResults(count: number, list: Party[]) {
if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
} else {
setParties([])
}
}
function appendResults(list: Party[]) {
setParties([...parties, ...list])
}
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
@ -149,16 +172,19 @@ const ProfileRoute: React.FC = () => {
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useEffect(() => { useEffect(() => {
if (!raidsLoading) fetchProfile() if (!raidsLoading) {
setCurrentPage(1)
fetchProfile({ replace: true })
}
}, [element, raid, recency]) }, [element, raid, recency])
// On first mount only, disable loading if we are fetching all teams
useEffect(() => { useEffect(() => {
if (raidSlug === 'all') { // Current page changed
setRaidsLoading(false) if (currentPage > 1)
fetchProfile() fetchProfile({ replace: false })
} else if (currentPage == 1)
}, []) fetchProfile({ replace: true })
}, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) { function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
@ -190,6 +216,22 @@ const ProfileRoute: React.FC = () => {
// TODO: Add save functions // TODO: Add save functions
function renderParties() {
return parties.map((party, i) => {
return <GridRep
id={party.id}
shortcode={party.shortcode}
name={party.name}
createdAt={new Date(party.created_at)}
raid={party.raid}
grid={party.weapons}
favorited={party.favorited}
key={`party-${i}`}
onClick={goTo}
/>
})
}
return ( return (
<div id="Profile"> <div id="Profile">
<Head> <Head>
@ -224,23 +266,16 @@ const ProfileRoute: React.FC = () => {
</FilterBar> </FilterBar>
<section> <section>
<GridRepCollection loading={loading}> <InfiniteScroll
{ dataLength={ (parties && parties.length > 0) ? parties.length : 0}
parties.map((party, i) => { next={ () => setCurrentPage(currentPage + 1) }
return <GridRep hasMore={totalPages > currentPage}
id={party.id} loader={ <div id="NotFound"><h2>Loading...</h2></div> }>
shortcode={party.shortcode} <GridRepCollection loading={loading}>
name={party.name} { renderParties() }
createdAt={new Date(party.created_at)} </GridRepCollection>
raid={party.raid} </InfiniteScroll>
grid={party.weapons}
favorited={party.favorited}
key={`party-${i}`}
onClick={goTo}
/>
})
}
</GridRepCollection>
{ (parties.length == 0) ? { (parties.length == 0) ?
<div id="NotFound"> <div id="NotFound">
<h2>{ (loading) ? t('teams.loading') : t('teams.not_found') }</h2> <h2>{ (loading) ? t('teams.loading') : t('teams.not_found') }</h2>

View file

@ -5,6 +5,7 @@ import { useCookies } from 'react-cookie'
import { queryTypes, useQueryState } from 'next-usequerystate' import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
@ -20,9 +21,7 @@ const SavedRoute: React.FC = () => {
// Set up cookies // Set up cookies
const [cookies] = useCookies(['account']) const [cookies] = useCookies(['account'])
const headers = (cookies.account) ? { const headers = (cookies.account) ? {
headers: { 'Authorization': `Bearer ${cookies.account.access_token}`
'Authorization': `Bearer ${cookies.account.access_token}`
}
} : {} } : {}
// Set up router // Set up router
@ -41,6 +40,11 @@ const SavedRoute: React.FC = () => {
const [raids, setRaids] = useState<Raid[]>() const [raids, setRaids] = useState<Raid[]>()
const [raid, setRaid] = useState<Raid>() const [raid, setRaid] = useState<Raid>()
// Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState("element", {
@ -87,26 +91,43 @@ const SavedRoute: React.FC = () => {
} }
}, []) }, [])
const fetchTeams = useCallback(() => { const fetchTeams = useCallback(({ replace }: { replace: boolean }) => {
const filters = { const filters = {
params: { params: {
element: (element != -1) ? element : undefined, element: (element != -1) ? element : undefined,
raid: (raid) ? raid?.id : undefined, raid: (raid) ? raid.id : undefined,
recency: (recency != -1) ? recency : undefined recency: (recency != -1) ? recency : undefined,
page: currentPage
} }
} }
api.savedTeams({...filters, ...headers}) api.savedTeams({...filters, ...{ headers: headers }})
.then(response => { .then(response => {
const parties: Party[] = response.data setTotalPages(response.data.total_pages)
setParties(parties.map((p: any) => p.party) setRecordCount(response.data.count)
.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
if (replace)
replaceResults(response.data.count, response.data.results)
else
appendResults(response.data.results)
}) })
.then(() => { .then(() => {
setLoading(false) setLoading(false)
}) })
.catch(error => handleError(error)) .catch(error => handleError(error))
}, [element, raid, recency]) }, [currentPage, parties, element, raid, recency])
function replaceResults(count: number, list: Party[]) {
if (count > 0) {
setParties(list)
} else {
setParties([])
}
}
function appendResults(list: Party[]) {
setParties([...parties, ...list])
}
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
@ -127,16 +148,19 @@ const SavedRoute: React.FC = () => {
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useEffect(() => { useEffect(() => {
if (!raidsLoading) fetchTeams() if (!raidsLoading) {
setCurrentPage(1)
fetchTeams({ replace: true })
}
}, [element, raid, recency]) }, [element, raid, recency])
// On first mount only, disable loading if we are fetching all teams
useEffect(() => { useEffect(() => {
if (raidSlug === 'all') { // Current page changed
setRaidsLoading(false) if (currentPage > 1)
fetchTeams() fetchTeams({ replace: false })
} else if (currentPage == 1)
}, []) fetchTeams({ replace: true })
}, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) { function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
@ -208,6 +232,24 @@ const SavedRoute: React.FC = () => {
router.push(`/p/${shortcode}`) router.push(`/p/${shortcode}`)
} }
function renderParties() {
return parties.map((party, i) => {
return <GridRep
id={party.id}
shortcode={party.shortcode}
name={party.name}
createdAt={new Date(party.created_at)}
raid={party.raid}
grid={party.weapons}
user={party.user}
favorited={party.favorited}
key={`party-${i}`}
displayUser={true}
onClick={goTo}
onSave={toggleFavorite} />
})
}
return ( return (
<div id="Teams"> <div id="Teams">
<Head> <Head>
@ -232,25 +274,15 @@ const SavedRoute: React.FC = () => {
</FilterBar> </FilterBar>
<section> <section>
<GridRepCollection loading={loading}> <InfiniteScroll
{ dataLength={ (parties && parties.length > 0) ? parties.length : 0}
parties.map((party, i) => { next={ () => setCurrentPage(currentPage + 1) }
return <GridRep hasMore={totalPages > currentPage}
id={party.id} loader={ <div id="NotFound"><h2>Loading...</h2></div> }>
shortcode={party.shortcode} <GridRepCollection loading={loading}>
name={party.name} { renderParties() }
createdAt={new Date(party.created_at)} </GridRepCollection>
raid={party.raid} </InfiniteScroll>
grid={party.weapons}
user={party.user}
favorited={party.favorited}
key={`party-${i}`}
displayUser={true}
onClick={goTo}
onSave={toggleFavorite} />
})
}
</GridRepCollection>
{ (parties.length == 0) ? { (parties.length == 0) ?
<div id="NotFound"> <div id="NotFound">

View file

@ -2,9 +2,10 @@ import React, { useCallback, useEffect, useState } from 'react'
import Head from 'next/head' import Head from 'next/head'
import { useCookies } from 'react-cookie' import { useCookies } from 'react-cookie'
import { useQueryState, queryTypes } from 'next-usequerystate' import { queryTypes, useQueryState } from 'next-usequerystate'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import InfiniteScroll from 'react-infinite-scroll-component'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
@ -20,9 +21,7 @@ const TeamsRoute: React.FC = () => {
// Set up cookies // Set up cookies
const [cookies] = useCookies(['account']) const [cookies] = useCookies(['account'])
const headers = (cookies.account) ? { const headers = (cookies.account) ? {
headers: { 'Authorization': `Bearer ${cookies.account.access_token}`
'Authorization': `Bearer ${cookies.account.access_token}`
}
} : {} } : {}
// Set up router // Set up router
@ -41,6 +40,11 @@ const TeamsRoute: React.FC = () => {
const [raids, setRaids] = useState<Raid[]>() const [raids, setRaids] = useState<Raid[]>()
const [raid, setRaid] = useState<Raid>() const [raid, setRaid] = useState<Raid>()
// Set up infinite scrolling-related states
const [recordCount, setRecordCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
// Set up filter-specific query states // Set up filter-specific query states
// Recency is in seconds // Recency is in seconds
const [element, setElement] = useQueryState("element", { const [element, setElement] = useQueryState("element", {
@ -87,26 +91,43 @@ const TeamsRoute: React.FC = () => {
} }
}, []) }, [])
const fetchTeams = useCallback(() => { const fetchTeams = useCallback(({ replace }: { replace: boolean }) => {
const filters = { const filters = {
params: { params: {
element: (element != -1) ? element : undefined, element: (element != -1) ? element : undefined,
raid: (raid) ? raid.id : undefined, raid: (raid) ? raid.id : undefined,
recency: (recency != -1) ? recency : undefined recency: (recency != -1) ? recency : undefined,
page: currentPage
} }
} }
api.endpoints.parties.getAll({...filters, ...headers}) api.endpoints.parties.getAll({...filters, ...{ headers: headers }})
.then(response => { .then(response => {
const parties: Party[] = response.data setTotalPages(response.data.total_pages)
setParties(parties.map((p: any) => p.party) setRecordCount(response.data.count)
.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
if (replace)
replaceResults(response.data.count, response.data.results)
else
appendResults(response.data.results)
}) })
.then(() => { .then(() => {
setLoading(false) setLoading(false)
}) })
.catch(error => handleError(error)) .catch(error => handleError(error))
}, [element, raid, recency]) }, [currentPage, parties, element, raid, recency])
function replaceResults(count: number, list: Party[]) {
if (count > 0) {
setParties(list.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
} else {
setParties([])
}
}
function appendResults(list: Party[]) {
setParties([...parties, ...list])
}
// Fetch all raids on mount, then find the raid in the URL if present // Fetch all raids on mount, then find the raid in the URL if present
useEffect(() => { useEffect(() => {
@ -127,16 +148,19 @@ const TeamsRoute: React.FC = () => {
// When the element, raid or recency filter changes, // When the element, raid or recency filter changes,
// fetch all teams again. // fetch all teams again.
useEffect(() => { useEffect(() => {
if (!raidsLoading) fetchTeams() if (!raidsLoading) {
setCurrentPage(1)
fetchTeams({ replace: true })
}
}, [element, raid, recency]) }, [element, raid, recency])
// On first mount only, disable loading if we are fetching all teams
useEffect(() => { useEffect(() => {
if (raidSlug === 'all') { // Current page changed
setRaidsLoading(false) if (currentPage > 1)
fetchTeams() fetchTeams({ replace: false })
} else if (currentPage == 1)
}, []) fetchTeams({ replace: true })
}, [currentPage])
// Receive filters from the filter bar // Receive filters from the filter bar
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) { function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
@ -208,6 +232,24 @@ const TeamsRoute: React.FC = () => {
router.push(`/p/${shortcode}`) router.push(`/p/${shortcode}`)
} }
function renderParties() {
return parties.map((party, i) => {
return <GridRep
id={party.id}
shortcode={party.shortcode}
name={party.name}
createdAt={new Date(party.created_at)}
raid={party.raid}
grid={party.weapons}
user={party.user}
favorited={party.favorited}
key={`party-${i}`}
displayUser={true}
onClick={goTo}
onSave={toggleFavorite} />
})
}
return ( return (
<div id="Teams"> <div id="Teams">
<Head> <Head>
@ -234,25 +276,15 @@ const TeamsRoute: React.FC = () => {
</FilterBar> </FilterBar>
<section> <section>
<GridRepCollection loading={loading}> <InfiniteScroll
{ dataLength={ (parties && parties.length > 0) ? parties.length : 0}
parties.map((party, i) => { next={ () => setCurrentPage(currentPage + 1) }
return <GridRep hasMore={totalPages > currentPage}
id={party.id} loader={ <div id="NotFound"><h2>Loading...</h2></div> }>
shortcode={party.shortcode} <GridRepCollection loading={loading}>
name={party.name} { renderParties() }
createdAt={new Date(party.created_at)} </GridRepCollection>
raid={party.raid} </InfiniteScroll>
grid={party.weapons}
user={party.user}
favorited={party.favorited}
key={`party-${i}`}
displayUser={true}
onClick={goTo}
onSave={toggleFavorite} />
})
}
</GridRepCollection>
{ (parties.length == 0) ? { (parties.length == 0) ?
<div id="NotFound"> <div id="NotFound">