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 type { Matches } from 'requests'
import { getMatches } from 'requests' import { getMatches } from 'requests'
import { useHeaderFiltersStore } from 'features/HeaderFilters' import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useRequest } from 'hooks'
const MATCHES_LIMIT = 60
export const useHomePage = () => { export const useHomePage = () => {
const { const {
@ -13,26 +21,66 @@ export const useHomePage = () => {
selectedTournamentId, selectedTournamentId,
} = useHeaderFiltersStore() } = useHeaderFiltersStore()
const {
isFetching,
request: requestMatches,
} = useRequest(getMatches)
const [matches, setMatches] = useState<Matches>({ const [matches, setMatches] = useState<Matches>({
broadcast: [], broadcast: [],
features: [], features: [],
hasNextPage: true,
highlights: [], highlights: [],
isVideoSections: false, isVideoSections: false,
}) })
const pageRef = useRef(0)
useEffect(() => { const fetchMatches = useCallback((page: number) => (
getMatches({ requestMatches({
date: selectedDateFormatted, date: selectedDateFormatted,
limit: MATCHES_LIMIT,
matchStatus: selectedMatchStatus, matchStatus: selectedMatchStatus,
offset: page * MATCHES_LIMIT,
sportType: selectedSportTypeId, sportType: selectedSportTypeId,
tournamentId: selectedTournamentId, tournamentId: selectedTournamentId,
}).then(setMatches) })
}, [ ), [
selectedDateFormatted, selectedDateFormatted,
selectedMatchStatus, selectedMatchStatus,
selectedSportTypeId, selectedSportTypeId,
selectedTournamentId, 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 React from 'react'
import { InfiniteScroll } from 'features/InfiniteScroll'
import { Matches } from 'features/Matches' import { Matches } from 'features/Matches'
import { useHomePage } from './hooks' import { useHomePage } from './hooks'
import { Content } from './styled' import { Content, Loading } from './styled'
export const HomePage = () => { export const HomePage = () => {
const { matches } = useHomePage() const {
fetchMoreMatches,
isFetching,
matches,
} = useHomePage()
return ( return (
<Content> <Content>
<Matches matches={matches} /> <InfiniteScroll onFetchMore={fetchMoreMatches}>
<Matches matches={matches} />
</InfiniteScroll>
{isFetching && <Loading>Loading...</Loading>}
</Content> </Content>
) )
} }

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

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

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

Loading…
Cancel
Save