From 0f3bcffb9f16ca0555652a57835883cd6ef8d5ae Mon Sep 17 00:00:00 2001 From: Caleb Eby Date: Fri, 3 Jul 2020 12:32:40 -0700 Subject: [PATCH 1/6] Distinguish between network errors, http errors, and loading state --- src/api/base.ts | 21 +++++++++++++++------ src/routes/event-match.tsx | 1 + src/utils/is-data.ts | 7 +++++++ src/utils/use-network-cache.ts | 31 +++++++++++++++++++++---------- 4 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 src/utils/is-data.ts diff --git a/src/api/base.ts b/src/api/base.ts index 3f68458c5..7f09e31c6 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -21,6 +21,10 @@ interface ErrorData { error: string } +export class NetworkError extends Error { + name = 'NetworkError' +} + export const request = ( method: HTTPMethod, endpoint: string, @@ -33,12 +37,17 @@ export const request = ( onCancel(() => controller.abort()) const jwt = await getWorkingJWT() - const resp = await fetch(apiUrl + endpoint + qs(params), { - method, - body: JSON.stringify(body), - headers: jwt ? { Authorization: `Bearer ${jwt.raw}` } : {}, - signal, - }) + let resp: Response + try { + resp = await fetch(apiUrl + endpoint + qs(params), { + method, + body: JSON.stringify(body), + headers: jwt ? { Authorization: `Bearer ${jwt.raw}` } : {}, + signal, + }) + } catch (e) { + throw new NetworkError(e.message) + } const text = await resp.text() diff --git a/src/routes/event-match.tsx b/src/routes/event-match.tsx index 2cc969b9e..8c71114a7 100644 --- a/src/routes/event-match.tsx +++ b/src/routes/event-match.tsx @@ -21,6 +21,7 @@ import { BooleanDisplay } from '@/components/boolean-display' import { matchHasTeam } from '@/utils/match-has-team' import { VideoCard } from '@/components/video-card' import { cleanYoutubeUrl } from '@/utils/clean-youtube-url' +import { isData } from '@/utils/is-data' interface Props { eventKey: string diff --git a/src/utils/is-data.ts b/src/utils/is-data.ts new file mode 100644 index 000000000..87244cd16 --- /dev/null +++ b/src/utils/is-data.ts @@ -0,0 +1,7 @@ +/** + * Returns whether the given input is not an error and is also not undefined + */ +export const isData = ( + input: T, +): input is Exclude => + !(input instanceof Error) && input !== undefined diff --git a/src/utils/use-network-cache.ts b/src/utils/use-network-cache.ts index 1dc89cfd2..2885ba25b 100644 --- a/src/utils/use-network-cache.ts +++ b/src/utils/use-network-cache.ts @@ -1,5 +1,6 @@ import { useState, useEffect } from 'preact/hooks' import { CancellablePromise } from './cancellable-promise' +import { NetworkError } from '@/api/base' /** * Returns a hook that executes a network promise and a cache promise. @@ -14,37 +15,47 @@ export const useNetworkCache = ( cacheGetter: (...args: ArgsType) => Promise, ) => { interface ResultingFunction { - (...args: ArgsType): DataType | undefined + (...args: ArgsType): DataType | undefined | NetworkError | Error ( ...args: { // Pass the first parameter as `undefined` to skip fetching [K in keyof ArgsType]: ArgsType[K] | undefined } - ): DataType | undefined + ): DataType | undefined | NetworkError | Error } /* eslint-disable caleb/react-hooks/rules-of-hooks, caleb/react-hooks/exhaustive-deps */ const resultingFunction: ResultingFunction = (...args: any[]) => { - const [data, setData] = useState(undefined) + const [networkData, setNetworkData] = useState< + DataType | undefined | NetworkError | Error + >(undefined) + const [cacheData, setCacheData] = useState(undefined) useEffect(() => { // When the args change, reset the data - setData(undefined) + setNetworkData(undefined) // Allow us to pass a single parameter of undefined to skip getting the data if (args.length >= 1 && args[0] === undefined) return cacheGetter(...(args as ArgsType)).then((cachedData) => - // don't update state using the cached data if network has already returned - setData((data) => data || cachedData), + setCacheData(cachedData), ) - const p = networkGetter(...(args as ArgsType)).then((networkData) => - setData(networkData), - ) + const p = networkGetter(...(args as ArgsType)) + .then((networkData) => setNetworkData(networkData)) + .catch((error) => { + if (error instanceof Error) { + console.log('got an error', error) + setNetworkData(error) + } + }) return () => p.cancel() }, args) - return data + if (networkData && !(networkData instanceof NetworkError)) { + return networkData + } + return cacheData } return resultingFunction /* eslint-enable caleb/react-hooks/rules-of-hooks, caleb/react-hooks/exhaustive-deps */ From 0da860bbdb37b8416061bb67b5b461df84673958 Mon Sep 17 00:00:00 2001 From: Caleb Eby Date: Fri, 3 Jul 2020 12:34:48 -0700 Subject: [PATCH 2/6] Remove console.log --- src/utils/use-network-cache.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/use-network-cache.ts b/src/utils/use-network-cache.ts index 2885ba25b..d32b7e08d 100644 --- a/src/utils/use-network-cache.ts +++ b/src/utils/use-network-cache.ts @@ -44,7 +44,6 @@ export const useNetworkCache = ( .then((networkData) => setNetworkData(networkData)) .catch((error) => { if (error instanceof Error) { - console.log('got an error', error) setNetworkData(error) } }) From 0358e817c3c4e2a941801197acd83cadb6dd56df Mon Sep 17 00:00:00 2001 From: Caleb Eby Date: Sun, 5 Jul 2020 16:09:19 -0700 Subject: [PATCH 3/6] Make usePromise return errors if there are errors --- src/utils/use-promise/index.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/utils/use-promise/index.tsx b/src/utils/use-promise/index.tsx index 816b31a7b..b402c6bfe 100644 --- a/src/utils/use-promise/index.tsx +++ b/src/utils/use-promise/index.tsx @@ -1,17 +1,27 @@ import { useState, useEffect } from 'preact/hooks' import { useErrorEmitter } from '@/components/error-boundary' import { CancellablePromise } from '@/utils/cancellable-promise' +import { NetworkError } from '@/api/base' export const usePromise = ( promiseCreator: () => Promise | T, dependencies: any[] = [promiseCreator], ) => { const emitError = useErrorEmitter() - const [val, setVal] = useState(undefined) + const [val, setVal] = useState( + undefined, + ) useEffect(() => { const cbResult = promiseCreator() if (!(cbResult instanceof Promise)) return setVal(cbResult) - cbResult.then((v) => setVal(v)).catch(emitError) + cbResult + .then((v) => setVal(v)) + .catch((error) => { + console.log('caught an error') + emitError(error) + // if (error instanceof Error) setVal(error) + // else emitError(error) + }) return () => { if (cbResult instanceof CancellablePromise) cbResult.cancel() setVal(undefined) From 196ef837bab5953c90626cf018869c9895da3ba9 Mon Sep 17 00:00:00 2001 From: var Cepheid Date: Wed, 19 Jan 2022 16:57:18 -0800 Subject: [PATCH 4/6] updated comment-card max width trying to fix issue 1480 --- src/components/report-viewer.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/report-viewer.tsx b/src/components/report-viewer.tsx index 9b07fdbd6..0194b0e47 100644 --- a/src/components/report-viewer.tsx +++ b/src/components/report-viewer.tsx @@ -59,6 +59,10 @@ const fieldValuesStyle = css` } ` +const reportCommentCard = css` + max-width: 10rem +` + export const ReportViewer = ({ report, onEditClick }: Props) => { const reporterId = report.reporterId const eventInfo = useEventInfo(report.eventKey) @@ -111,11 +115,11 @@ export const ReportViewer = ({ report, onEditClick }: Props) => { {report.comment && ( - report={report} showReporter={false} linkToReport={false} - /> + )} From 8e955dc6cc15b14ffc50b6a42d60d7b8f959fb71 Mon Sep 17 00:00:00 2001 From: var Cepheid Date: Mon, 16 May 2022 17:32:33 -0700 Subject: [PATCH 5/6] fixed Error bugs and ran lint --- src/api/base.ts | 4 +- src/components/analysis-table.tsx | 6 ++- src/components/chart.tsx | 9 +++- src/components/comment-card.tsx | 7 ++- src/components/profile-link.tsx | 3 +- src/components/report-editor/index.tsx | 46 +++++++++++++----- src/components/report-viewer.tsx | 13 ++--- src/routes/event-analysis.tsx | 7 +-- src/routes/event-match.tsx | 67 ++++++++++++++++++-------- src/routes/event-team-comments.tsx | 20 ++++---- src/routes/event-team-matches.tsx | 6 ++- src/routes/event-team.tsx | 14 +++--- src/routes/event.tsx | 7 +-- src/routes/home.tsx | 6 ++- src/routes/leaderboard.tsx | 15 ++++-- src/routes/signup.tsx | 7 ++- src/routes/user-reports.tsx | 8 ++- src/routes/user.tsx | 5 +- src/routes/users.tsx | 3 +- src/utils/is-data.ts | 3 +- src/utils/use-years.ts | 3 +- 21 files changed, 177 insertions(+), 82 deletions(-) diff --git a/src/api/base.ts b/src/api/base.ts index 41eb91456..d2c45fbb1 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -45,8 +45,8 @@ export const request = ( headers: jwt ? { Authorization: `Bearer ${jwt.raw}` } : {}, signal, }) - } catch (e) { - throw new NetworkError(e.message) + } catch (error_) { + throw new NetworkError(error_.message) } const text = await resp.text() diff --git a/src/components/analysis-table.tsx b/src/components/analysis-table.tsx index 388b545b2..2057f936b 100644 --- a/src/components/analysis-table.tsx +++ b/src/components/analysis-table.tsx @@ -25,6 +25,7 @@ import { usePromise } from '@/utils/use-promise' import { getEventTeams } from '@/api/event-team-info/get-event-teams' import { eventTeamUrl } from '@/utils/urls/event-team' import { EventTeamInfo } from '@/api/event-team-info' +import { isData } from '@/utils/is-data' interface Props { eventKey: string @@ -224,7 +225,10 @@ const AnalysisTable = ({ const rankColumn: Column = { title: 'Rank', key: 'Rank', - getCell: (row) => rankingInfo?.find((r) => r.team === 'frc' + row.team), + getCell: (row) => + isData(rankingInfo) + ? rankingInfo.find((r) => r.team === 'frc' + row.team) + : undefined, getCellValue: (cell) => cell?.rank ?? Infinity, renderCell: (cell) => ( diff --git a/src/components/chart.tsx b/src/components/chart.tsx index aec5d3311..83a7c9a3c 100644 --- a/src/components/chart.tsx +++ b/src/components/chart.tsx @@ -24,6 +24,7 @@ import { cleanFieldName } from '@/utils/clean-field-name' import { getFieldKey } from '@/utils/get-field-key' import { getReports } from '@/api/report/get-reports' import { GetReport } from '@/api/report' +import { isData } from '@/utils/is-data' const commentsDisplayStyle = css` grid-column: 1 / -1; @@ -106,7 +107,7 @@ export const ChartCard = ({ return matchesAutoFieldName || matchesTeleopFieldName })?.name - const matchesWithSelectedStat = (matchesStats || []) + const matchesWithSelectedStat = (isData(matchesStats) ? matchesStats : []) .map(({ matchKey, stats }) => { const matchingStat = stats.find((f) => f.name === fullFieldName) if (matchingStat) return { matchKey, matchingStat } @@ -190,7 +191,11 @@ export const ChartCard = ({ {selectedMatchKey && ( r.matchKey === selectedMatchKey)} + reports={ + isData(allReports) + ? allReports.filter((r) => r.matchKey === selectedMatchKey) + : [] + } /> )} diff --git a/src/components/comment-card.tsx b/src/components/comment-card.tsx index 26ef25a3c..3e0872835 100644 --- a/src/components/comment-card.tsx +++ b/src/components/comment-card.tsx @@ -6,6 +6,7 @@ import Icon from './icon' import { mdiMessageReplyText } from '@mdi/js' import { formatUserName } from '@/utils/format-user-name' import { getFastestUser } from '@/cache/users/get-fastest' +import { isData } from '@/utils/is-data' const commentCardStyle = css` display: grid; @@ -52,7 +53,11 @@ export const CommentCard = ({ class={commentCardStyle} > - {showReporter && {formatUserName(reporter, reporterId)}} + {showReporter && ( + + {formatUserName(isData(reporter) ? reporter : undefined, reporterId)} + + )}

{report.comment}

) diff --git a/src/components/profile-link.tsx b/src/components/profile-link.tsx index b5648476a..dad873970 100644 --- a/src/components/profile-link.tsx +++ b/src/components/profile-link.tsx @@ -5,6 +5,7 @@ import { css } from 'linaria' import { mdiAccountCircle } from '@mdi/js' import { pigmicePurple } from '@/colors' import { getFastestUser } from '@/cache/users/get-fastest' +import { isData } from '@/utils/is-data' export const ProfileLink = ({ reporterId, @@ -26,7 +27,7 @@ export const ProfileLink = ({ class={reporterStyle} > - {formatUserName(reporter, reporterId)} + {formatUserName(isData(reporter) ? reporter : undefined, reporterId)} ) } diff --git a/src/components/report-editor/index.tsx b/src/components/report-editor/index.tsx index adab69335..74c743ad8 100644 --- a/src/components/report-editor/index.tsx +++ b/src/components/report-editor/index.tsx @@ -32,6 +32,7 @@ import { createAlert } from '@/router' import { AlertType } from '../alert' import Loader from '../loader' import Card from '../card' +import { isData } from '@/utils/is-data' const reportEditorStyle = css` padding: 1.5rem 2rem; @@ -71,6 +72,7 @@ const userDropdownStyle = css` align-items: center; ` +// eslint-disable-next-line complexity export const ReportEditor = ({ initialReport, onMatchSelect, @@ -84,10 +86,16 @@ export const ReportEditor = ({ const [isDeleting, setIsDeleting] = useState(false) const [matchKey, setMatchKey] = useState(initialReport.matchKey) const [comment, setComment] = useState(initialReport.comment || '') - const schemaId = useEventInfo(eventKey)?.schemaId - const schema = useSchema(schemaId)?.schema + + const eventInfo = useEventInfo(eventKey) + const schemaId = isData(eventInfo) ? eventInfo.schemaId : undefined + const testSchema = useSchema(schemaId) + const schema = isData(testSchema) ? testSchema.schema : undefined + const eventMatches = useEventMatches(eventKey) - const match = eventMatches?.find((match) => matchKey === match.key) + const match = isData(eventMatches) + ? eventMatches.find((match) => matchKey === match.key) + : undefined const { jwt } = useJWT() const isAdmin = jwt?.peregrineRoles.isAdmin const users = usePromise(() => getUsers(), []) @@ -228,14 +236,18 @@ export const ReportEditor = ({ return eventMatches && match && visibleFields ? ( { onMatchSelect?.(match.key) setMatchKey(match.key) }} getKey={(match) => match.key} getText={(match) => formatMatchKeyShort(match.key)} - value={eventMatches.find((match) => match.key === matchKey)} + value={ + isData(eventMatches) + ? eventMatches.find((match) => match.key === matchKey) + : undefined + } /> - a.firstName.toLowerCase() > b.firstName.toLowerCase() ? 1 : -1, - )} + options={ + isData(users) + ? users.sort((a, b) => + a.firstName.toLowerCase() > b.firstName.toLowerCase() + ? 1 + : -1, + ) + : [] + } onChange={(user) => { setReporterId(user.id) setRealmId(user.realmId) @@ -281,10 +299,16 @@ export const ReportEditor = ({ getKey={(user) => user.id} getText={(user) => `${user.firstName} ${user.lastName}`} getGroup={(user) => - realms?.find((realm) => realm.id === user.realmId)?.name || - String(user.realmId) + isData(realms) + ? realms.find((realm) => realm.id === user.realmId)?.name || + String(user.realmId) + : null + } + value={ + isData(users) + ? users.find((user) => user.id === reporterId) + : undefined } - value={users.find((user) => user.id === reporterId)} /> )} diff --git a/src/components/report-viewer.tsx b/src/components/report-viewer.tsx index 5a8e9dcd3..84f9b497e 100644 --- a/src/components/report-viewer.tsx +++ b/src/components/report-viewer.tsx @@ -12,6 +12,7 @@ import { CommentCard } from './comment-card' import { css } from 'linaria' import { cleanFieldName } from '@/utils/clean-field-name' import { ProfileLink } from './profile-link' +import { isData } from '@/utils/is-data' interface Props { report: Report @@ -66,10 +67,10 @@ export const ReportViewer = ({ report, onEditClick }: Props) => { const reporterId = report.reporterId const eventInfo = useEventInfo(report.eventKey) const matchInfo = useMatchInfo(report.eventKey, report.matchKey) - const schema = useSchema(eventInfo?.schemaId) - const displayableFields = schema?.schema.filter( - (field) => field.reportReference !== undefined, - ) + const schema = useSchema(isData(eventInfo) ? eventInfo.schemaId : undefined) + const displayableFields = isData(schema) + ? schema.schema.filter((field) => field.reportReference !== undefined) + : undefined const autoFields = displayableFields?.filter( (field) => field.period === 'auto', ) @@ -99,8 +100,8 @@ export const ReportViewer = ({ report, onEditClick }: Props) => {
{matchInfo && ( diff --git a/src/routes/event-analysis.tsx b/src/routes/event-analysis.tsx index 94ba5454e..db3a0898b 100644 --- a/src/routes/event-analysis.tsx +++ b/src/routes/event-analysis.tsx @@ -13,6 +13,7 @@ import { tablePageTableStyle, } from '@/utils/table-page-style' import Card from '@/components/card' +import { isData } from '@/utils/is-data' interface Props { eventKey: string @@ -26,7 +27,7 @@ const EventAnalysis: FunctionComponent = ({ eventKey }) => { const eventStats = usePromise(() => getEventStats(eventKey), [eventKey]) const eventInfo = useEventInfo(eventKey) - const schema = useSchema(eventInfo?.schemaId) + const schema = useSchema(isData(eventInfo) ? eventInfo.schemaId : undefined) return ( = ({ eventKey }) => { class={tablePageStyle} wrapperClass={tablePageWrapperStyle} > - {eventStats && schema ? ( + {isData(eventStats) && schema ? ( eventStats.length === 0 ? ( 'No Event Data' ) : ( @@ -43,7 +44,7 @@ const EventAnalysis: FunctionComponent = ({ eventKey }) => { ( {team} diff --git a/src/routes/event-match.tsx b/src/routes/event-match.tsx index 26dbb932e..f3fbb0918 100644 --- a/src/routes/event-match.tsx +++ b/src/routes/event-match.tsx @@ -127,12 +127,15 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { const m = formatMatchKey(matchKey) const event = useEventInfo(eventKey) const match = useMatchInfo(eventKey, matchKey) + const matchRedAlliance = isData(match) ? match.redAlliance : undefined const reports = usePromise(() => { if (isOnline) { return getReports({ event: eventKey, match: matchKey }) } }, [eventKey, matchKey, isOnline]) - const schema = useSchema(isOnline ? event?.schemaId : undefined) + const schema = useSchema( + isOnline && isData(event) ? event.schemaId : undefined, + ) const eventTeamsStats = usePromise(() => { if (isOnline) { return getEventStats(eventKey) @@ -143,8 +146,9 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { showEventResults, ) - const matchHasBeenPlayed = - match?.blueScore !== undefined && match.redScore !== undefined + const matchHasBeenPlayed = isData(match) + ? match.blueScore !== undefined && match.redScore !== undefined + : undefined // When the match loads (or changes), useEffect(() => { @@ -152,7 +156,7 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { }, [matchHasBeenPlayed]) const teamsStats = usePromise(() => { - if (match && isOnline) { + if (isData(match) && isOnline) { return Promise.all( [...match.redAlliance, ...match.blueAlliance].map((t) => getMatchTeamStats(eventKey, match.key, t).then(processTeamStats), @@ -174,7 +178,8 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { class={clsx( matchStyle, match && loadedMatchStyle, - match?.videos && + isData(match) && + match.videos && match.videos.length > 0 && isOnline && matchWithVideoStyle, @@ -212,10 +217,21 @@ const EventMatch = ({ eventKey, matchKey }: Props) => {
)} - - {reports && reports.length > 0 ? ( + + {isData(reports) && reports.length > 0 ? ( @@ -227,8 +243,12 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { )} {matchHasBeenPlayed /* final score if the match is over */ && ( -
{match.redScore}
-
{match.blueScore}
+
+ {isData(match) && match.redScore} +
+
+ {isData(match) && match.blueScore} +
)} @@ -266,17 +286,25 @@ const EventMatch = ({ eventKey, matchKey }: Props) => { eventKey={eventKey} teams={ selectedDisplay === showEventResults - ? eventTeamsStats?.filter((t) => - matchHasTeam('frc' + t.team)(match), - ) - : teamsStats + ? isData(eventTeamsStats) + ? eventTeamsStats.filter((t) => + matchHasTeam('frc' + t.team)( + isData(match) + ? match + : { key: '', redAlliance: [], blueAlliance: [] }, + ), + ) + : undefined + : isData(teamsStats) + ? teamsStats + : undefined } - schema={schema} + schema={isData(schema) ? schema : { id: -1, schema: [] }} renderTeam={(team, link) => (
{ )} {/* shows videos if the match has them and online */} - {isOnline && match.videos && match.videos.length > 0 && ( - - )} + {isOnline && + isData(match) && + match.videos && + match.videos.length > 0 && } ) : ( diff --git a/src/routes/event-team-comments.tsx b/src/routes/event-team-comments.tsx index b9d4341d2..8b1ece34b 100644 --- a/src/routes/event-team-comments.tsx +++ b/src/routes/event-team-comments.tsx @@ -10,6 +10,7 @@ import { formatMatchKeyShort } from '@/utils/format-match-key-short' import { compareMatchKeys } from '@/utils/compare-matches' import { GetReport } from '@/api/report' import { getReports } from '@/api/report/get-reports' +import { isData } from '@/utils/is-data' interface Props { eventKey: string @@ -33,16 +34,15 @@ const EventTeamComments = ({ eventKey, teamNum }: Props) => { team, eventKey, ]) - const commentsByMatch = reports?.reduce<{ [matchKey: string]: GetReport[] }>( - (acc, report) => { - if (report.comment) { - // eslint-disable-next-line caleb/@typescript-eslint/no-unnecessary-condition - ;(acc[report.matchKey] || (acc[report.matchKey] = [])).push(report) - } - return acc - }, - {}, - ) + const commentsByMatch = isData(reports) + ? reports.reduce<{ [matchKey: string]: GetReport[] }>((acc, report) => { + if (report.comment) { + // eslint-disable-next-line caleb/@typescript-eslint/no-unnecessary-condition + ;(acc[report.matchKey] || (acc[report.matchKey] = [])).push(report) + } + return acc + }, {}) + : undefined return ( { class={eventTeamMatchesStyle} > {matches ? ( - + ) : ( )} diff --git a/src/routes/event-team.tsx b/src/routes/event-team.tsx index 510af1d07..818a4371e 100644 --- a/src/routes/event-team.tsx +++ b/src/routes/event-team.tsx @@ -27,6 +27,7 @@ import { useCurrentTime } from '@/utils/use-current-time' import { saveTeam, useSavedTeams, removeTeam } from '@/api/save-teams' import IconButton from '@/components/icon-button' import { EventTeamInfo } from '@/api/event-team-info' +import { isData } from '@/utils/is-data' const sectionStyle = css` font-weight: normal; @@ -162,10 +163,11 @@ const EventTeam = ({ eventKey, teamNum }: Props) => { () => getEventTeamInfo(eventKey, 'frc' + teamNum).catch(() => undefined), [eventKey, teamNum], ) - const schema = useSchema(eventInfo?.schemaId) - const teamMatches = useEventMatches(eventKey, 'frc' + teamNum)?.sort( - compareMatches, - ) + const schema = useSchema(isData(eventInfo) ? eventInfo.schemaId : undefined) + let teamMatches = useEventMatches(eventKey, 'frc' + teamNum) + teamMatches = isData(teamMatches) + ? teamMatches.sort(compareMatches) + : undefined const nextMatch = teamMatches && nextIncompleteMatch(teamMatches) @@ -203,7 +205,7 @@ const EventTeam = ({ eventKey, teamNum }: Props) => { )} @@ -79,7 +80,7 @@ const Event = ({ eventKey }: Props) => { link /> )} - {matches ? ( + {isData(matches) ? ( matches.length > 0 ? ( ) : ( diff --git a/src/routes/home.tsx b/src/routes/home.tsx index 194f3a4e7..280f35ea6 100644 --- a/src/routes/home.tsx +++ b/src/routes/home.tsx @@ -13,6 +13,7 @@ import { UnstyledList } from '@/components/unstyled-list' import { useYears } from '@/utils/use-years' import IconButton from '@/components/icon-button' import { mdiCrosshairsGps } from '@mdi/js' +import { isData } from '@/utils/is-data' const homeStyle = css` display: grid; @@ -46,7 +47,8 @@ const Home = () => { const [location, prompt] = useGeoLocation() const [query, setQuery] = useState('') const lowerCaseQuery = query.toLowerCase() - const years = useYears().sort().reverse() + let years = useYears() + years = isData(years) ? years.sort().reverse() : [] const [yearVal, setYear] = useQueryState('year', years[0]) const year = Number(yearVal) const events = useEvents(year) @@ -65,7 +67,7 @@ const Home = () => { )} - {events ? ( + {isData(events) ? ( <> {events diff --git a/src/routes/leaderboard.tsx b/src/routes/leaderboard.tsx index a9744839c..6e527ec08 100644 --- a/src/routes/leaderboard.tsx +++ b/src/routes/leaderboard.tsx @@ -10,6 +10,7 @@ import { useYears } from '@/utils/use-years' import { Dropdown } from '@/components/dropdown' import { getFastestUser } from '@/cache/users/get-fastest' import { UserInfo } from '@/api/user' +import { isData } from '@/utils/is-data' const leaderboardCardTitleStyle = css` font-weight: 500; @@ -58,10 +59,16 @@ const LeaderboardList = () => { return (
- - {leaderboard?.map((user) => ( - - )) || } + + {isData(leaderboard) ? ( + leaderboard.map((user) => ) + ) : ( + + )}
) } diff --git a/src/routes/signup.tsx b/src/routes/signup.tsx index 66f49899b..5a8a051f5 100644 --- a/src/routes/signup.tsx +++ b/src/routes/signup.tsx @@ -19,6 +19,7 @@ import { ErrorBoundary, useErrorEmitter } from '@/components/error-boundary' import { authenticate } from '@/api/authenticate' import { route } from '@/router' import { Realm } from '@/api/realm' +import { isData } from '@/utils/is-data' const signUpStyle = css` padding: 1.5rem; @@ -91,10 +92,12 @@ const SignUpForm = () => { maxLength={maxPasswordLength} /> - value={realms.find((r) => r.id === realmId)} + value={ + isData(realms) ? realms.find((r) => r.id === realmId) : undefined + } emptyLabel="Select a realm" class={dropdownClass} - options={realms} + options={isData(realms) ? realms : []} required onChange={(v) => setRealmId(v.id)} getKey={(v) => v.id} diff --git a/src/routes/user-reports.tsx b/src/routes/user-reports.tsx index c2558a250..8ff5cfda7 100644 --- a/src/routes/user-reports.tsx +++ b/src/routes/user-reports.tsx @@ -9,6 +9,7 @@ import { GetReport } from '@/api/report' import { useEventInfo } from '@/cache/event-info/use' import { formatTeamNumber } from '@/utils/format-team-number' import { getFastestUser } from '@/cache/users/get-fastest' +import { isData } from '@/utils/is-data' interface Props { userId: string @@ -36,12 +37,15 @@ const UserReports = ({ userId }: Props) => { return ( - {reports ? ( + {isData(reports) ? ( reports.map((report) => { return }) diff --git a/src/routes/user.tsx b/src/routes/user.tsx index 86e05bbf7..8bcc23089 100644 --- a/src/routes/user.tsx +++ b/src/routes/user.tsx @@ -34,6 +34,7 @@ import clsx from 'clsx' import { getReports } from '@/api/report/get-reports' import { noop } from '@/utils/empty-promise' import { getFastestUser } from '@/cache/users/get-fastest' +import { isData } from '@/utils/is-data' const RoleInfo = ({ save, @@ -336,7 +337,7 @@ const UserProfileCard = ({ )} - {reports && ( + {isData(reports) && (