diff --git a/src/features/HomePage/hooks.tsx b/src/features/HomePage/hooks.tsx index 6607d6a2..cb3e32cc 100644 --- a/src/features/HomePage/hooks.tsx +++ b/src/features/HomePage/hooks.tsx @@ -1,17 +1,6 @@ -import { - useCallback, - useEffect, - useState, - useRef, -} from 'react' - -import type { Matches } from 'requests' -import { getMatches } from 'requests' +import { useMemo } from 'react' import { useHeaderFiltersStore } from 'features/HeaderFilters' -import { useRequest } from 'hooks' - -const MATCHES_LIMIT = 60 export const useHomePage = () => { const { @@ -21,66 +10,17 @@ export const useHomePage = () => { selectedTournamentId, } = useHeaderFiltersStore() - const { - isFetching, - request: requestMatches, - } = useRequest(getMatches) - - const [matches, setMatches] = useState({ - broadcast: [], - features: [], - hasNextPage: true, - highlights: [], - isVideoSections: false, - }) - const pageRef = useRef(0) - - const fetchMatches = useCallback((page: number) => ( - requestMatches({ - date: selectedDateFormatted, - limit: MATCHES_LIMIT, - matchStatus: selectedMatchStatus, - offset: page * MATCHES_LIMIT, - sportType: selectedSportTypeId, - tournamentId: selectedTournamentId, - }) - ), [ + const requestArgs = useMemo(() => ({ + date: selectedDateFormatted, + matchStatus: selectedMatchStatus, + sportType: selectedSportTypeId, + tournamentId: selectedTournamentId, + }), [ 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, - ]) - - useEffect(() => { - fetchMatches(0).then(setMatches) - pageRef.current = 1 - }, [fetchMatches]) - - return { - fetchMoreMatches, - isFetching, - matches, - } + return { requestArgs } } diff --git a/src/features/HomePage/index.tsx b/src/features/HomePage/index.tsx index ef5a28f0..33d60acb 100644 --- a/src/features/HomePage/index.tsx +++ b/src/features/HomePage/index.tsx @@ -1,23 +1,15 @@ import React from 'react' -import { InfiniteScroll } from 'features/InfiniteScroll' import { Matches } from 'features/Matches' import { useHomePage } from './hooks' -import { Content, Loading } from './styled' +import { Content } from './styled' export const HomePage = () => { - const { - fetchMoreMatches, - isFetching, - matches, - } = useHomePage() + const { requestArgs } = useHomePage() return ( - - - - {isFetching && Loading...} + ) } diff --git a/src/features/InfiniteScroll/hooks.tsx b/src/features/InfiniteScroll/hooks.tsx index 8444bc9b..e83dace8 100644 --- a/src/features/InfiniteScroll/hooks.tsx +++ b/src/features/InfiniteScroll/hooks.tsx @@ -16,11 +16,16 @@ type Args = { * пересечение с областью видимости документа */ export const useIntersectionObserver = ({ onIntersect, options }: Args) => { + const callbackRef = useRef(onIntersect) const rootRef = useRef(null) const targetRef = useRef(null) const rootElement = rootRef.current const targetElement = targetRef.current + useEffect(() => { + callbackRef.current = onIntersect + }, [onIntersect]) + useEffect(() => { if (!targetElement) { return undefined @@ -29,7 +34,7 @@ export const useIntersectionObserver = ({ onIntersect, options }: Args) => { const observerOptions = { root: rootElement, ...options } const callback = ([target]: Array) => { if (target.isIntersecting) { - onIntersect(target, observer) + callbackRef.current(target, observer) } } @@ -42,7 +47,6 @@ export const useIntersectionObserver = ({ onIntersect, options }: Args) => { }, [ rootElement, targetElement, - onIntersect, options, ]) diff --git a/src/features/Matches/components/MatchesList/index.tsx b/src/features/Matches/components/MatchesList/index.tsx new file mode 100644 index 00000000..73acdd1c --- /dev/null +++ b/src/features/Matches/components/MatchesList/index.tsx @@ -0,0 +1,39 @@ +import React, { Fragment } from 'react' + +import isEmpty from 'lodash/isEmpty' + +import type { Match } from 'features/Matches/hooks' +import { MatchesSlider } from 'features/MatchesSlider' +import { MatchesGrid } from 'features/MatchesGrid' +import { T9n } from 'features/T9n' + +import { Title, Section } from '../../styled' + +const matchesComponents = { + grid: MatchesGrid, + slider: MatchesSlider, +} + +type Props = { + as: keyof typeof matchesComponents, + matches: Array, + title: string, +} + +export const MatchesList = ({ + as, + matches, + title, +}: Props) => { + const Component = matchesComponents[as] + return ( + + {!isEmpty(matches) && ( +
+ <T9n t={title} /> + +
+ )} +
+ ) +} diff --git a/src/features/Matches/helpers.tsx b/src/features/Matches/helpers.tsx new file mode 100644 index 00000000..3455f24b --- /dev/null +++ b/src/features/Matches/helpers.tsx @@ -0,0 +1,49 @@ +import map from 'lodash/map' +import flatten from 'lodash/flatten' +import pipe from 'lodash/fp/pipe' +import fpMap from 'lodash/fp/map' + +import type { Content } from 'requests' +import { ProfileTypes } from 'config' +import { getProfileLogo, getSportLexic } from 'helpers' + +type Name = 'name_rus' | 'name_eng' + +const prepareMatch = ({ + matches: matchesList, + sport, + ...rest +}: Content, suffix: string) => map(matchesList, ({ + date, + id, + stream_status, + team1, + team2, +}) => ({ + date, + id, + preview: '/images/preview.png', + sportName: getSportLexic(sport), + sportType: sport, + streamStatus: stream_status, + team1Logo: getProfileLogo({ + id: team1.id, + profileType: ProfileTypes.TEAMS, + sportType: sport, + }), + team1Name: team1[`name_${suffix}` as Name], + team1Score: team1.score, + team2Logo: getProfileLogo({ + id: team2.id, + profileType: ProfileTypes.TEAMS, + sportType: sport, + }), + team2Name: team2[`name_${suffix}` as Name], + team2Score: team2.score, + tournamentName: rest[`name_${suffix}` as Name], +})) + +export const prepareMatches = (content: Array, suffix: string) => pipe( + fpMap((items: Content) => prepareMatch(items, suffix)), + flatten, +)(content) diff --git a/src/features/Matches/hooks.tsx b/src/features/Matches/hooks.tsx index 83bfa82f..aea0f6aa 100644 --- a/src/features/Matches/hooks.tsx +++ b/src/features/Matches/hooks.tsx @@ -1,82 +1,93 @@ -import { useMemo } from 'react' +import { + useCallback, + useEffect, + useState, + useMemo, + useRef, +} from 'react' -import map from 'lodash/map' -import flatten from 'lodash/flatten' -import pipe from 'lodash/fp/pipe' -import fpMap from 'lodash/fp/map' +import type { Matches } from 'requests' +import { getMatches } from 'requests' + +import { useRequest } from 'hooks' -import type { Matches, Content } from 'requests' -import { ProfileTypes } from 'config' -import { getProfileLogo, getSportLexic } from 'helpers' import { useLexicsStore } from 'features/LexicsStore' -import { MatchStatuses } from 'features/HeaderFilters' + +import { prepareMatches } from './helpers' + +export type Match = ReturnType[number] + +type RequestArgs = Omit[0], 'limit' | 'offset'> export type Props = { - matches: Matches, + requestArgs: RequestArgs, } -type Name = 'name_rus' | 'name_eng' - -export type Match = { - date: string, - id: number, - preview: string, - sportName: string, - sportType: number, - streamStatus: MatchStatuses, - team1Logo: string, - team1Name: string, - team1Score: number, - team2Logo: string, - team2Name: string, - team2Score: number, - tournamentName: string, -} +const MATCHES_LIMIT = 60 -const prepareMatches = (content: Array, suffix: string) => pipe( - fpMap>(({ - matches: matchesList, - sport, - ...rest - }) => map(matchesList, ({ - date, - id, - stream_status, - team1, - team2, - }) => ({ - date, - id, - preview: '/images/preview.png', - sportName: getSportLexic(sport), - sportType: sport, - streamStatus: stream_status, - team1Logo: getProfileLogo({ - id: team1.id, - profileType: ProfileTypes.TEAMS, - sportType: sport, - }), - team1Name: team1[`name_${suffix}` as Name], - team1Score: team1.score, - team2Logo: getProfileLogo({ - id: team2.id, - profileType: ProfileTypes.TEAMS, - sportType: sport, - }), - team2Name: team2[`name_${suffix}` as Name], - team2Score: team2.score, - tournamentName: rest[`name_${suffix}` as Name], - }))), - flatten, -)(content) as Array - -export const useMatches = ({ matches }: Props) => { +export const useMatches = ({ requestArgs }: Props) => { const { suffix } = useLexicsStore() + const { + isFetching, + request: requestMatches, + } = useRequest(getMatches) + + const pageRef = useRef(0) + const [matches, setMatches] = useState({ + broadcast: [], + features: [], + hasNextPage: true, + highlights: [], + isVideoSections: false, + }) - return useMemo(() => ({ + const { hasNextPage } = matches + + const fetchMatches = useCallback((page: number) => ( + requestMatches({ + ...requestArgs, + limit: MATCHES_LIMIT, + offset: page * MATCHES_LIMIT, + }) + ), [ + requestMatches, + requestArgs, + ]) + + 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, + ]) + + useEffect(() => { + fetchMatches(0).then(setMatches) + pageRef.current = 1 + }, [fetchMatches]) + + const preparedMatches = useMemo(() => ({ broadcast: prepareMatches(matches.broadcast, suffix), features: prepareMatches(matches.features, suffix), highlights: prepareMatches(matches.highlights, suffix), isVideoSections: matches.isVideoSections, }), [suffix, matches]) + + return { + fetchMoreMatches, + isFetching, + matches: preparedMatches, + } } diff --git a/src/features/Matches/index.tsx b/src/features/Matches/index.tsx index 8fd1fefa..80b6340e 100644 --- a/src/features/Matches/index.tsx +++ b/src/features/Matches/index.tsx @@ -1,60 +1,56 @@ import React, { Fragment } from 'react' -import isEmpty from 'lodash/isEmpty' - -import { MatchesSlider } from 'features/MatchesSlider' -import { MatchesGrid } from 'features/MatchesGrid' -import { T9n } from 'features/T9n' +import { InfiniteScroll } from 'features/InfiniteScroll' +import { Loading } from 'features/HomePage/styled' import type { Props } from './hooks' import { useMatches } from './hooks' -import { Title, Section } from './styled' +import { MatchesList } from './components/MatchesList' export type { Match } from './hooks' export const Matches = (props: Props) => { const { - broadcast, - features, - highlights, - isVideoSections, + fetchMoreMatches, + isFetching, + matches: { + broadcast, + features, + highlights, + isVideoSections, + }, } = useMatches(props) if (isVideoSections) { return ( - {!isEmpty(broadcast) && ( -
- <T9n t='broadcast' /> - -
- )} - - {!isEmpty(highlights) && ( -
- <T9n t='round_highilights' /> - -
- )} - - {!isEmpty(features) && ( -
- <T9n t='features' /> - -
- )} + + +
) } return ( - - {!isEmpty(broadcast) && ( -
- <T9n t='broadcast' /> - -
- )} -
+ + + {isFetching && Loading...} + ) } diff --git a/src/features/PlayerPage/hooks.tsx b/src/features/PlayerPage/hooks.tsx index 1828fac7..b663903c 100644 --- a/src/features/PlayerPage/hooks.tsx +++ b/src/features/PlayerPage/hooks.tsx @@ -1,14 +1,15 @@ -import { useEffect, useState } from 'react' +import { + useEffect, + useState, + useMemo, +} from 'react' import { useSportNameParam, usePageId } from 'hooks' -import type { Matches } from 'requests' -import { getMatches } from 'requests' import type { PlayerProfile } from 'requests/getPlayerInfo' import { getPlayerInfo } from 'requests/getPlayerInfo' import { useLexicsStore } from 'features/LexicsStore' -import { useHeaderFiltersStore } from 'features/HeaderFilters' type Firstname = 'firstname_eng' | 'firstname_rus' type Lastname = 'lastname_eng' | 'lastname_rus' @@ -17,12 +18,8 @@ type Name = 'name_eng' | 'name_rus' export const usePlayerPage = () => { const [playerProfile, setPlayerProfile] = useState(null) const { sportType } = useSportNameParam() - const pageId = usePageId() + const playerId = usePageId() const { suffix, translate } = useLexicsStore() - const { - selectedDateFormatted, - selectedMatchStatus, - } = useHeaderFiltersStore() const { club_team, @@ -42,33 +39,17 @@ export const usePlayerPage = () => { ] useEffect(() => { - getPlayerInfo(pageId, sportType).then(setPlayerProfile) - }, [pageId, sportType]) - - const [matches, setMatches] = useState({ - broadcast: [], - features: [], - highlights: [], - isVideoSections: false, - }) + getPlayerInfo(playerId, sportType).then(setPlayerProfile) + }, [playerId, sportType]) - useEffect(() => { - getMatches({ - date: selectedDateFormatted, - matchStatus: selectedMatchStatus, - playerId: pageId, - sportType, - }).then(setMatches) - }, [ - pageId, - selectedDateFormatted, - selectedMatchStatus, + const requestArgs = useMemo(() => ({ + playerId, sportType, - ]) + }), [playerId, sportType]) return { infoItems, - matches, name: fullName, + requestArgs, } } diff --git a/src/features/PlayerPage/index.tsx b/src/features/PlayerPage/index.tsx index 4587151f..4343ebe9 100644 --- a/src/features/PlayerPage/index.tsx +++ b/src/features/PlayerPage/index.tsx @@ -9,8 +9,8 @@ import { usePlayerPage } from './hooks' export const PlayerPage = () => { const { infoItems, - matches, name, + requestArgs, } = usePlayerPage() return ( @@ -20,7 +20,7 @@ export const PlayerPage = () => { name={name} infoItems={infoItems} /> - + ) } diff --git a/src/features/TeamPage/hooks.tsx b/src/features/TeamPage/hooks.tsx index 32b3d0d2..4d370b7b 100644 --- a/src/features/TeamPage/hooks.tsx +++ b/src/features/TeamPage/hooks.tsx @@ -1,9 +1,12 @@ -import { useEffect, useState } from 'react' +import { + useEffect, + useState, + useMemo, +} from 'react' -import type { TeamInfo, Matches } from 'requests' -import { getTeamInfo, getMatches } from 'requests' +import type { TeamInfo } from 'requests' +import { getTeamInfo } from 'requests' -import { useHeaderFiltersStore } from 'features/HeaderFilters' import { useLexicsStore } from 'features/LexicsStore' import { useSportNameParam, usePageId } from 'hooks' @@ -16,18 +19,6 @@ export const useTeamPage = () => { const teamId = usePageId() const { suffix } = useLexicsStore() - const { - selectedDateFormatted, - selectedMatchStatus, - } = useHeaderFiltersStore() - - const [matches, setMatches] = useState({ - broadcast: [], - features: [], - highlights: [], - isVideoSections: false, - }) - const { [`name_${suffix}` as Name]: name = '', } = teamProfile || {} @@ -45,24 +36,15 @@ export const useTeamPage = () => { teamId, ]) - useEffect(() => { - getMatches({ - date: selectedDateFormatted, - matchStatus: selectedMatchStatus, - sportType, - teamId, - }).then(setMatches) - }, [ - selectedDateFormatted, - selectedMatchStatus, + const requestArgs = useMemo(() => ({ sportType, teamId, - ]) + }), [teamId, sportType]) return { infoItems: [country], - matches, name, + requestArgs, sportType, } } diff --git a/src/features/TeamPage/index.tsx b/src/features/TeamPage/index.tsx index 2a124883..47805c5f 100644 --- a/src/features/TeamPage/index.tsx +++ b/src/features/TeamPage/index.tsx @@ -11,8 +11,8 @@ import { Content } from './styled' export const TeamPage = () => { const { infoItems, - matches, name, + requestArgs, } = useTeamPage() return ( @@ -23,7 +23,7 @@ export const TeamPage = () => { name={name} infoItems={infoItems} /> - +
) diff --git a/src/features/TournamentPage/hooks.tsx b/src/features/TournamentPage/hooks.tsx index 878c71ed..5fd4a7a8 100644 --- a/src/features/TournamentPage/hooks.tsx +++ b/src/features/TournamentPage/hooks.tsx @@ -1,10 +1,13 @@ -import { useEffect, useState } from 'react' +import { + useEffect, + useState, + useMemo, +} from 'react' -import { useHeaderFiltersStore } from 'features/HeaderFilters' import { useLexicsStore } from 'features/LexicsStore' -import type { TournamentInfo, Matches } from 'requests' -import { getTournamentInfo, getMatches } from 'requests' +import type { TournamentInfo } from 'requests' +import { getTournamentInfo } from 'requests' import { useSportNameParam, usePageId } from 'hooks' @@ -16,18 +19,6 @@ export const useTournamentPage = () => { const tournamentId = usePageId() const { suffix } = useLexicsStore() - const { - selectedDateFormatted, - selectedMatchStatus, - } = useHeaderFiltersStore() - - const [matches, setMatches] = useState({ - broadcast: [], - features: [], - highlights: [], - isVideoSections: false, - }) - const { [`name_${suffix}` as Name]: name = '', } = tournamentProfile || {} @@ -45,23 +36,14 @@ export const useTournamentPage = () => { tournamentId, ]) - useEffect(() => { - getMatches({ - date: selectedDateFormatted, - matchStatus: selectedMatchStatus, - sportType, - tournamentId, - }).then(setMatches) - }, [ - selectedDateFormatted, - selectedMatchStatus, + const requestArgs = useMemo(() => ({ sportType, tournamentId, - ]) + }), [tournamentId, sportType]) return { infoItems: [country], - matches, name, + requestArgs, } } diff --git a/src/features/TournamentPage/index.tsx b/src/features/TournamentPage/index.tsx index 4d0559b8..41ba841a 100644 --- a/src/features/TournamentPage/index.tsx +++ b/src/features/TournamentPage/index.tsx @@ -11,8 +11,8 @@ import { Content } from './styled' export const TournamentPage = () => { const { infoItems, - matches, name, + requestArgs, } = useTournamentPage() return ( @@ -23,7 +23,7 @@ export const TournamentPage = () => { name={name} infoItems={infoItems} /> - +
) diff --git a/src/requests/getMatches.tsx b/src/requests/getMatches.tsx index b7afe624..32bd498d 100644 --- a/src/requests/getMatches.tsx +++ b/src/requests/getMatches.tsx @@ -51,9 +51,9 @@ type Team = { } type Args = { - date: string, + date?: string, limit?: number, - matchStatus: MatchStatuses | null, + matchStatus?: MatchStatuses | null, offset?: number, playerId?: number | null, sportType: SportTypes | null,