Ott 337 matches lazy request (#122)

keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 5 years ago committed by GitHub
parent bfa7a214da
commit 70d9c924f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      src/features/HomePage/hooks.tsx
  2. 14
      src/features/HomePage/index.tsx
  3. 8
      src/features/HomePage/styled.tsx
  4. 52
      src/features/InfiniteScroll/hooks.tsx
  5. 29
      src/features/InfiniteScroll/index.tsx
  6. 8
      src/features/InfiniteScroll/styled.tsx
  7. 16
      src/features/MatchPage/hooks.tsx
  8. 2
      src/features/Matches/hooks.tsx
  9. 20
      src/requests/getMatches.tsx

@ -1,9 +1,17 @@
import { useEffect, useState } from 'react'
import {
useCallback,
useEffect,
useState,
useRef,
} from 'react'
import type { Matches } from 'requests'
import { getMatches } from 'requests'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useRequest } from 'hooks'
const MATCHES_LIMIT = 60
export const useHomePage = () => {
const {
@ -13,26 +21,66 @@ export const useHomePage = () => {
selectedTournamentId,
} = useHeaderFiltersStore()
const {
isFetching,
request: requestMatches,
} = useRequest(getMatches)
const [matches, setMatches] = useState<Matches>({
broadcast: [],
features: [],
hasNextPage: true,
highlights: [],
isVideoSections: false,
})
const pageRef = useRef(0)
useEffect(() => {
getMatches({
const fetchMatches = useCallback((page: number) => (
requestMatches({
date: selectedDateFormatted,
limit: MATCHES_LIMIT,
matchStatus: selectedMatchStatus,
offset: page * MATCHES_LIMIT,
sportType: selectedSportTypeId,
tournamentId: selectedTournamentId,
}).then(setMatches)
}, [
})
), [
selectedDateFormatted,
selectedMatchStatus,
selectedSportTypeId,
selectedTournamentId,
requestMatches,
])
const { hasNextPage } = matches
const fetchMoreMatches = useCallback(async () => {
if (!hasNextPage || isFetching) return
const newMatches = await fetchMatches(pageRef.current)
setMatches((oldMatches): Matches => {
const broadcast = [...oldMatches.broadcast, ...newMatches.broadcast]
return {
...oldMatches,
broadcast,
hasNextPage: newMatches.hasNextPage,
}
})
pageRef.current += 1
}, [
fetchMatches,
hasNextPage,
isFetching,
])
return { matches }
useEffect(() => {
fetchMatches(0).then(setMatches)
pageRef.current = 1
}, [fetchMatches])
return {
fetchMoreMatches,
isFetching,
matches,
}
}

@ -1,15 +1,23 @@
import React from 'react'
import { InfiniteScroll } from 'features/InfiniteScroll'
import { Matches } from 'features/Matches'
import { useHomePage } from './hooks'
import { Content } from './styled'
import { Content, Loading } from './styled'
export const HomePage = () => {
const { matches } = useHomePage()
const {
fetchMoreMatches,
isFetching,
matches,
} = useHomePage()
return (
<Content>
<Matches matches={matches} />
<InfiniteScroll onFetchMore={fetchMoreMatches}>
<Matches matches={matches} />
</InfiniteScroll>
{isFetching && <Loading>Loading...</Loading>}
</Content>
)
}

@ -13,3 +13,11 @@ export const Content = styled.main`
padding: 0;
}
`
export const Loading = styled.div`
height: 30px;
margin-top: 20px;
font-size: 24px;
color: #fff;
text-align: center;
`

@ -0,0 +1,52 @@
import { useEffect, useRef } from 'react'
import noop from 'lodash/noop'
type Args = {
onIntersect: (
target: IntersectionObserverEntry,
observer: IntersectionObserver,
) => void,
options?: IntersectionObserverInit,
}
/**
* Хук для отслежения пересечение targetRef с rootRef
* targetRef нужно повесить на целевой элемент который будет наблюдаться,
* rootRef на корневой элемент с которым пересекается targetRef.
* Также можно проигнорить rootRef тогда по-умолчанию отслеживается
* пересечение с областью видимости документа
*/
export const useIntersectionObserver = ({ onIntersect, options }: Args) => {
const rootRef = useRef<HTMLDivElement>(null)
const targetRef = useRef<HTMLDivElement>(null)
const rootElement = rootRef.current
const targetElement = targetRef.current
useEffect(() => {
if (!targetElement) {
return noop
}
const observerOptions = { root: rootElement, ...options }
const callback = ([target]: Array<IntersectionObserverEntry>) => {
if (target.isIntersecting) {
onIntersect(target, observer)
}
}
const observer = new IntersectionObserver(callback, observerOptions)
observer.observe(targetElement)
return () => {
observer.disconnect()
}
}, [
rootElement,
targetElement,
onIntersect,
options,
])
return { rootRef, targetRef }
}

@ -0,0 +1,29 @@
import type { ReactNode } from 'react'
import React from 'react'
import { useIntersectionObserver } from './hooks'
import { Root, Target } from './styled'
type Props = {
children: ReactNode,
onFetchMore: () => void,
options?: IntersectionObserverInit,
}
export const InfiniteScroll = ({
children,
onFetchMore,
options,
}: Props) => {
const { targetRef } = useIntersectionObserver({
onIntersect: onFetchMore,
options,
})
return (
<Root>
{children}
<Target ref={targetRef} />
</Root>
)
}

@ -0,0 +1,8 @@
import styled from 'styled-components/macro'
export const Root = styled.div``
export const Target = styled.div`
position: relative;
bottom: 20vh;
`

@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'
import type { MatchInfo } from 'requests'
import { getMatchInfo } from 'requests'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useLexicsStore } from 'features/LexicsStore'
import { useSportNameParam, usePageId } from 'hooks'
@ -16,11 +15,6 @@ export const useMatchPage = () => {
const pageId = usePageId()
const { suffix } = useLexicsStore()
const {
setSelectedSportTypeId,
setSelectedTournamentId,
} = useHeaderFiltersStore()
const matchProfileNames = {
team1Name: matchProfile?.team1[`name_${suffix}` as Name],
team2Name: matchProfile?.team2[`name_${suffix}` as Name],
@ -28,22 +22,12 @@ export const useMatchPage = () => {
}
useEffect(() => {
setSelectedSportTypeId(sportType)
setSelectedTournamentId(pageId)
getMatchInfo(sportType, pageId)
.then(setMatchProfile)
return () => {
setSelectedSportTypeId(null)
setSelectedTournamentId(null)
}
},
[
sportType,
pageId,
setSelectedSportTypeId,
setSelectedTournamentId,
])
return {

@ -4,7 +4,6 @@ import map from 'lodash/map'
import flatten from 'lodash/flatten'
import pipe from 'lodash/fp/pipe'
import fpMap from 'lodash/fp/map'
import fpOrderBy from 'lodash/fp/orderBy'
import type { Matches, Content } from 'requests'
import { ProfileTypes } from 'config'
@ -69,7 +68,6 @@ const prepareMatches = (content: Array<Content>, suffix: string) => pipe(
tournamentName: rest[`name_${suffix}` as Name],
}))),
flatten,
fpOrderBy((match: Match) => Number(new Date(match.date)), 'desc'),
)(content) as Array<Match>
export const useMatches = ({ matches }: Props) => {

@ -9,18 +9,19 @@ import { MatchStatuses } from 'features/HeaderFilters'
const proc = PROCEDURES.get_matches
export type Data = {
type Data = {
is_video_sections: boolean,
video_content: VideoContent,
}
export type VideoContent = {
type VideoContent = {
broadcast: Items,
features: Items,
highlights: Items,
show: boolean,
}
export type Items = {
type Items = {
content: Array<Content> | null,
name: string,
}
@ -33,7 +34,7 @@ export type Content = {
sport: SportTypes,
}
export type Match = {
type Match = {
date: string,
id: number,
round_id: number | null,
@ -42,7 +43,7 @@ export type Match = {
team2: Team,
}
export type Team = {
type Team = {
id: number,
name_eng: string,
name_rus: string,
@ -51,7 +52,9 @@ export type Team = {
type Args = {
date: string,
limit?: number,
matchStatus: MatchStatuses | null,
offset?: number,
playerId?: number | null,
sportType: SportTypes | null,
teamId?: number | null,
@ -61,13 +64,16 @@ type Args = {
export type Matches = {
broadcast: Array<Content>,
features: Array<Content>,
hasNextPage?: boolean,
highlights: Array<Content>,
isVideoSections: boolean,
}
export const getMatches = async ({
date,
limit,
matchStatus,
offset,
playerId,
sportType,
teamId,
@ -77,6 +83,8 @@ export const getMatches = async ({
body: {
params: {
_p_date: date,
_p_limit: limit,
_p_offset: offset,
_p_player_id: playerId || null,
_p_sport: sportType,
_p_stream_status: matchStatus,
@ -96,11 +104,13 @@ export const getMatches = async ({
broadcast,
features,
highlights,
show,
} = data.video_content
return {
broadcast: broadcast.content || [],
features: features.content || [],
hasNextPage: Boolean(show),
highlights: highlights.content || [],
isVideoSections: data.is_video_sections,
}

Loading…
Cancel
Save