Ott 464 infinite scroll in tournaments filter (#166)

* fix(#464): lazy ref problem

* refactor(#464): used InfiniteScroll component in tournaments filter list

* Update src/features/InfiniteScroll/index.tsx

Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru>

Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru>
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 5 years ago committed by GitHub
parent d767f266f2
commit a5e2aaabd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      src/features/HeaderFilters/components/TournamentFilter/hooks.tsx
  2. 8
      src/features/HeaderFilters/components/TournamentFilter/index.tsx
  3. 3
      src/features/HeaderFilters/components/TournamentFilter/styled.tsx
  4. 75
      src/features/HeaderFilters/components/TournamentList/index.tsx
  5. 13
      src/features/HeaderFilters/store/hooks/index.tsx
  6. 10
      src/features/InfiniteScroll/hooks.tsx
  7. 13
      src/features/InfiniteScroll/index.tsx
  8. 2
      src/features/Matches/index.tsx
  9. 10
      src/requests/getSportTournaments.tsx

@ -1,10 +1,11 @@
import type { BaseSyntheticEvent, MouseEvent } from 'react'
import type { MouseEvent } from 'react'
import { useEffect, useState } from 'react'
import find from 'lodash/find'
import type { Tournaments } from 'requests'
import { getSportTournaments } from 'requests'
import { useToggle } from 'hooks'
import { useRequest, useToggle } from 'hooks'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useLexicsStore } from 'features/LexicsStore'
@ -26,25 +27,26 @@ export const useTournamentFilter = () => {
isOpen,
open,
} = useToggle()
const {
isFetching,
request: requestTournaments,
} = useRequest(getSportTournaments)
const [page, setPage] = useState(0)
useEffect(() => {
setList([])
getSportTournaments(selectedSportTypeId).then(setList)
}, [selectedSportTypeId])
const [requestSent, setRequestSent] = useState(false)
requestTournaments(selectedSportTypeId, 0).then((tournaments) => {
setList(tournaments)
setPage(1)
})
}, [selectedSportTypeId, requestTournaments])
const handleScroll = (e: BaseSyntheticEvent) => {
const scrollPositionBottom = e.target.scrollHeight - e.target.scrollTop
- e.target.clientHeight - 400 <= 0
const fetchMore = async () => {
if (isFetching) return
if (scrollPositionBottom && !requestSent) {
setRequestSent(true)
getSportTournaments(selectedSportTypeId).then((res) => {
setList([...list, ...res])
setRequestSent(false)
})
}
const newTournaments = await requestTournaments(selectedSportTypeId, page)
setList([...list, ...newTournaments])
setPage(page + 1)
}
const tournaments = normalizeTournaments(list, suffix)
@ -66,7 +68,7 @@ export const useTournamentFilter = () => {
return {
close,
handleScroll,
fetchMore,
isOpen,
onResetSelectedTournament,
onTournamentSelect,

@ -7,7 +7,7 @@ import { TournamentList } from '../TournamentList'
import { useTournamentFilter } from './hooks'
import {
Wrapper,
ListWrapper,
InfiniteScroll,
DropdownButton,
ButtonTitle,
Arrows,
@ -17,7 +17,7 @@ import {
export const TournamentFilter = () => {
const {
close,
handleScroll,
fetchMore,
isOpen,
onResetSelectedTournament,
onTournamentSelect,
@ -50,12 +50,12 @@ export const TournamentFilter = () => {
{
isOpen && (
<OutsideClick onClick={close}>
<ListWrapper id='tournamentsList' onScroll={handleScroll}>
<InfiniteScroll onFetchMore={fetchMore}>
<TournamentList
tournaments={tournaments}
onSelect={onTournamentSelect}
/>
</ListWrapper>
</InfiniteScroll>
</OutsideClick>
)
}

@ -2,8 +2,9 @@ import styled from 'styled-components/macro'
import { devices } from 'config/devices'
import { customScrollbar } from 'features/Common'
import { InfiniteScroll as InfiniteScrollBase } from 'features/InfiniteScroll'
export const ListWrapper = styled.div`
export const InfiniteScroll = styled(InfiniteScrollBase)`
position: absolute;
left: 0;
top: calc(100% + 8px);

@ -1,10 +1,9 @@
import React from 'react'
import React, { SyntheticEvent } from 'react'
import map from 'lodash/map'
import { SportTypes } from 'config'
import { useItemsList } from 'features/ItemsList/hooks'
import {
Logo,
LogoWrapper,
@ -31,41 +30,39 @@ type TournamentListProps = {
tournaments: Array<Tournament>,
}
export const TournamentList = ({ onSelect, tournaments }: TournamentListProps) => {
const {
onError,
ref,
} = useItemsList()
return (
<Wrapper ref={ref} role='listbox'>
{map(tournaments, ({
country,
fallbackImage,
id,
logo,
name,
sportType,
}) => (
<ListItem
key={`${id}_${name}`}
onClick={() => onSelect(id)}
role='option'
>
<LogoWrapper>
<Logo
data-src={logo}
onError={onError(fallbackImage)}
alt={name}
/>
</LogoWrapper>
<ItemInfo>
<Name>{name}</Name>
<SportName sport={sportType} />
<TeamOrCountry>{country}</TeamOrCountry>
</ItemInfo>
</ListItem>
))}
</Wrapper>
)
const onError = (fallbackSrc: string) => (e: SyntheticEvent<HTMLImageElement>) => {
// eslint-disable-next-line no-param-reassign
e.currentTarget.src = fallbackSrc
}
export const TournamentList = ({ onSelect, tournaments }: TournamentListProps) => (
<Wrapper role='listbox'>
{map(tournaments, ({
country,
fallbackImage,
id,
logo,
name,
sportType,
}) => (
<ListItem
key={`${id}_${name}`}
onClick={() => onSelect(id)}
role='option'
>
<LogoWrapper>
<Logo
src={logo}
onError={onError(fallbackImage)}
alt={name}
/>
</LogoWrapper>
<ItemInfo>
<Name>{name}</Name>
<SportName sport={sportType} />
<TeamOrCountry>{country}</TeamOrCountry>
</ItemInfo>
</ListItem>
))}
</Wrapper>
)

@ -1,6 +1,9 @@
import { useMemo, useCallback } from 'react'
import {
useMemo,
useState,
useCallback,
} from 'react'
import isNumber from 'lodash/isNumber'
import format from 'date-fns/format'
import startOfDay from 'date-fns/startOfDay'
@ -60,11 +63,7 @@ export const useFilters = () => {
const [
selectedTournamentId,
setSelectedTournamentId,
] = useQueryParamStore<number | null>({
defaultValue: null,
key: filterKeys.TOURNAMENT_ID,
validator: isNumber,
})
] = useState<number | null>(null)
const store = useMemo(() => ({
selectedDate,

@ -19,14 +19,14 @@ export const useIntersectionObserver = ({ onIntersect, options }: Args) => {
const callbackRef = useRef(onIntersect)
const rootRef = useRef<HTMLDivElement>(null)
const targetRef = useRef<HTMLDivElement>(null)
const rootElement = rootRef.current
const targetElement = targetRef.current
useEffect(() => {
callbackRef.current = onIntersect
}, [onIntersect])
useEffect(() => {
const rootElement = rootRef.current
const targetElement = targetRef.current
if (!targetElement) {
return undefined
}
@ -44,11 +44,7 @@ export const useIntersectionObserver = ({ onIntersect, options }: Args) => {
return () => {
observer.disconnect()
}
}, [
rootElement,
targetElement,
options,
])
}, [options])
return { rootRef, targetRef }
}

@ -6,22 +6,31 @@ import { Root, Target } from './styled'
type Props = {
children: ReactNode,
className?: string,
fullPageScroll?: boolean,
onFetchMore: () => void,
options?: IntersectionObserverInit,
}
export const InfiniteScroll = ({
children,
className,
fullPageScroll,
onFetchMore,
options,
}: Props) => {
const { targetRef } = useIntersectionObserver({
const { rootRef, targetRef } = useIntersectionObserver({
onIntersect: onFetchMore,
options,
})
return (
<Root>
<Root
// игнорируем rootRef, чтобы отслеживать пересечение target
// с областью видимости document, т.е. всей страницы
ref={fullPageScroll ? null : rootRef}
className={className}
>
{children}
<Target ref={targetRef} />
</Root>

@ -44,7 +44,7 @@ export const Matches = (props: Props) => {
}
return (
<InfiniteScroll onFetchMore={fetchMoreMatches}>
<InfiniteScroll fullPageScroll onFetchMore={fetchMoreMatches}>
<MatchesList
as='grid'
title='broadcast'

@ -27,15 +27,17 @@ export type Tournament = {
export type Tournaments = Array<Tournament>
const LIMIT = 20
export const getSportTournaments = (
sportId?: number | null,
offset: number = 0,
sportId: SportTypes | null,
page: number = 0,
): Promise<Tournaments> => {
const config = {
body: {
params: {
_p_limit: 20,
_p_offset: offset,
_p_limit: LIMIT,
_p_offset: page * LIMIT,
_p_sport: sportId,
},
proc,

Loading…
Cancel
Save