From c4d0dd568ed01c5298730ac39fa6f55ca30c8042 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 22 Mar 2023 19:05:57 -0500 Subject: [PATCH] EntityStore: let totalCount be undefined, don't try to set it from the local count --- app/soapbox/entity-store/hooks/useEntities.ts | 5 +++-- app/soapbox/entity-store/reducer.ts | 10 ++++++++-- app/soapbox/entity-store/types.ts | 2 +- app/soapbox/entity-store/utils.ts | 6 ++++-- app/soapbox/utils/numbers.tsx | 4 ++++ 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index 996ee716f..6c8511e65 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -4,6 +4,7 @@ import z from 'zod'; import { getNextLink, getPrevLink } from 'soapbox/api'; import { useApi, useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks'; import { filteredArray } from 'soapbox/schemas/utils'; +import { realNumberSchema } from 'soapbox/utils/numbers'; import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions'; @@ -63,12 +64,12 @@ function useEntities( const response = await api.get(url); const schema = opts.schema || z.custom(); const entities = filteredArray(schema).parse(response.data); - const numItems = (selectList(getState(), path)?.ids.size || 0) + entities.length; + const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']); dispatch(entitiesFetchSuccess(entities, entityType, listKey, { next: getNextLink(response), prev: getPrevLink(response), - totalCount: Number(response.headers['x-total-count'] ?? numItems) || 0, + totalCount: parsedCount.success ? parsedCount.data : undefined, fetching: false, fetched: true, error: null, diff --git a/app/soapbox/entity-store/reducer.ts b/app/soapbox/entity-store/reducer.ts index 082204e88..7559b66a7 100644 --- a/app/soapbox/entity-store/reducer.ts +++ b/app/soapbox/entity-store/reducer.ts @@ -71,7 +71,10 @@ const deleteEntities = ( for (const list of Object.values(cache.lists)) { if (list) { list.ids.delete(id); - list.state.totalCount--; + + if (typeof list.state.totalCount === 'number') { + list.state.totalCount--; + } } } } @@ -94,7 +97,10 @@ const dismissEntities = ( if (list) { for (const id of ids) { list.ids.delete(id); - list.state.totalCount--; + + if (typeof list.state.totalCount === 'number') { + list.state.totalCount--; + } } draft[entityType] = cache; diff --git a/app/soapbox/entity-store/types.ts b/app/soapbox/entity-store/types.ts index 67f37180d..006b13ba2 100644 --- a/app/soapbox/entity-store/types.ts +++ b/app/soapbox/entity-store/types.ts @@ -24,7 +24,7 @@ interface EntityListState { /** Previous URL for pagination, if any. */ prev: string | undefined /** Total number of items according to the API. */ - totalCount: number + totalCount: number | undefined /** Error returned from the API, if any. */ error: any /** Whether data has already been fetched */ diff --git a/app/soapbox/entity-store/utils.ts b/app/soapbox/entity-store/utils.ts index cd023cc9c..e108639c2 100644 --- a/app/soapbox/entity-store/utils.ts +++ b/app/soapbox/entity-store/utils.ts @@ -13,8 +13,10 @@ const updateList = (list: EntityList, entities: Entity[]): EntityList => { const newIds = entities.map(entity => entity.id); const ids = new Set([...Array.from(list.ids), ...newIds]); - const sizeDiff = ids.size - list.ids.size; - list.state.totalCount += sizeDiff; + if (typeof list.state.totalCount === 'number') { + const sizeDiff = ids.size - list.ids.size; + list.state.totalCount += sizeDiff; + } return { ...list, diff --git a/app/soapbox/utils/numbers.tsx b/app/soapbox/utils/numbers.tsx index c4c6aaf75..004d25603 100644 --- a/app/soapbox/utils/numbers.tsx +++ b/app/soapbox/utils/numbers.tsx @@ -1,9 +1,13 @@ import React from 'react'; import { FormattedNumber } from 'react-intl'; +import { z } from 'zod'; /** Check if a value is REALLY a number. */ export const isNumber = (value: unknown): value is number => typeof value === 'number' && !isNaN(value); +/** The input is a number and is not NaN. */ +export const realNumberSchema = z.coerce.number().refine(n => !isNaN(n)); + export const secondsToDays = (seconds: number) => Math.floor(seconds / (3600 * 24)); const roundDown = (num: number) => {