Skip to content

Commit 47cb2f6

Browse files
committed
fix: prevent double loading during PayrollConfiguration pagination
- Switch from useEmployeesListSuspense to useEmployeesList with keepPreviousData - Add isDataInSync to detect when prepared payroll matches current employees - Add syncedEmployeeData state that only updates when both datasets sync - Use displayedEmployees for rendering to prevent mismatched data flash - Combined loading state shows spinner until both fetches complete - Add null check for initial load
1 parent 79de570 commit 47cb2f6

File tree

1 file changed

+51
-26
lines changed

1 file changed

+51
-26
lines changed

src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useMemo, useState, type ReactNode } from 'react'
2-
import { useEmployeesListSuspense } from '@gusto/embedded-api/react-query/employeesList'
2+
import { useEmployeesList } from '@gusto/embedded-api/react-query/employeesList'
3+
import { keepPreviousData } from '@tanstack/react-query'
34
import { usePayrollsGetSuspense } from '@gusto/embedded-api/react-query/payrollsGet'
45
import { usePayrollsCalculateMutation } from '@gusto/embedded-api/react-query/payrollsCalculate'
56
import type { Employee } from '@gusto/embedded-api/models/components/employee'
@@ -18,6 +19,7 @@ import { componentEvents } from '@/shared/constants'
1819
import { useComponentDictionary, useI18n } from '@/i18n'
1920
import { useBase } from '@/components/Base'
2021
import type { PaginationItemsPerPage } from '@/components/Common/PaginationControl/PaginationControlTypes'
22+
import { Loading } from '@/components/Common'
2123
import { useDateFormatter } from '@/hooks/useDateFormatter'
2224

2325
const isCalculating = (processingRequest?: PayrollProcessingRequest | null) =>
@@ -60,20 +62,22 @@ export const Root = ({
6062
const [isPolling, setIsPolling] = useState(false)
6163
const [payrollBlockers, setPayrollBlockers] = useState<ApiPayrollBlocker[]>([])
6264

63-
const { data: employeeData, isFetching: isFetchingEmployeeData } = useEmployeesListSuspense({
64-
companyId,
65-
payrollUuid: payrollId, // get back list of employees to specific payroll
66-
per: itemsPerPage,
67-
page: currentPage,
68-
sortBy: 'name', // sort alphanumeric by employee last_names
69-
})
65+
const { data: employeeData, isFetching: isFetchingEmployeeData } = useEmployeesList(
66+
{
67+
companyId,
68+
payrollUuid: payrollId,
69+
per: itemsPerPage,
70+
page: currentPage,
71+
sortBy: 'name',
72+
},
73+
{ placeholderData: keepPreviousData },
74+
)
7075

71-
// get list of employee uuids to filter into prepare endpoint to get back employee_compensation data
7276
const employeeUuids = useMemo(() => {
73-
return employeeData.showEmployees?.map(e => e.uuid) || []
74-
}, [employeeData.showEmployees])
77+
return employeeData?.showEmployees?.map(e => e.uuid) || []
78+
}, [employeeData?.showEmployees])
7579

76-
const totalPages = Number(employeeData.httpMeta.response.headers.get('x-total-pages') ?? 1)
80+
const totalPages = Number(employeeData?.httpMeta.response.headers.get('x-total-pages') ?? 1)
7781

7882
const handleItemsPerPageChange = (newCount: PaginationItemsPerPage) => {
7983
setItemsPerPage(newCount)
@@ -91,18 +95,6 @@ export const Root = ({
9195
setCurrentPage(totalPages)
9296
}
9397

94-
const pagination = {
95-
currentPage,
96-
handleFirstPage,
97-
handlePreviousPage,
98-
handleNextPage,
99-
handleLastPage,
100-
handleItemsPerPageChange,
101-
totalPages,
102-
isFetching: isFetchingEmployeeData,
103-
itemsPerPage,
104-
}
105-
10698
const { data: payrollData } = usePayrollsGetSuspense(
10799
{
108100
companyId,
@@ -128,6 +120,35 @@ export const Root = ({
128120
sortBy: 'last_name', // sort alphanumeric by employee last_names to match employees GET
129121
})
130122

123+
const isDataInSync = useMemo(() => {
124+
if (!preparedPayroll?.employeeCompensations || employeeUuids.length === 0) return false
125+
const preparedUuids = new Set(preparedPayroll.employeeCompensations.map(c => c.employeeUuid))
126+
return employeeUuids.every(uuid => preparedUuids.has(uuid))
127+
}, [preparedPayroll?.employeeCompensations, employeeUuids])
128+
129+
const [syncedEmployeeData, setSyncedEmployeeData] = useState(employeeData)
130+
131+
useEffect(() => {
132+
if (isDataInSync && employeeData) {
133+
setSyncedEmployeeData(employeeData)
134+
}
135+
}, [isDataInSync, employeeData])
136+
137+
const displayedEmployees = syncedEmployeeData?.showEmployees || []
138+
const isPaginationFetching = isFetchingEmployeeData || isPrepareLoading || !isDataInSync
139+
140+
const pagination = {
141+
currentPage,
142+
handleFirstPage,
143+
handlePreviousPage,
144+
handleNextPage,
145+
handleLastPage,
146+
handleItemsPerPageChange,
147+
totalPages,
148+
isFetching: isPaginationFetching,
149+
itemsPerPage,
150+
}
151+
131152
const onCalculatePayroll = async () => {
132153
// Clear any existing blockers before attempting calculation
133154
setPayrollBlockers([])
@@ -239,20 +260,24 @@ export const Root = ({
239260
}
240261
: undefined
241262

263+
if (!employeeData) {
264+
return <Loading />
265+
}
266+
242267
return (
243268
<PayrollConfigurationPresentation
244269
onCalculatePayroll={onCalculatePayroll}
245270
onEdit={onEdit}
246271
onToggleExclude={onToggleExclude}
247272
onViewBlockers={onViewBlockers}
248273
employeeCompensations={preparedPayroll?.employeeCompensations || []}
249-
employeeDetails={employeeData.showEmployees || []}
274+
employeeDetails={displayedEmployees}
250275
payPeriod={preparedPayroll?.payPeriod}
251276
paySchedule={paySchedule}
252277
isOffCycle={preparedPayroll?.offCycle}
253278
alerts={alerts}
254279
payrollDeadlineNotice={payrollDeadlineNotice}
255-
isPending={isPolling || isPrepareLoading || isUpdatingPayroll}
280+
isPending={isPolling || (isPrepareLoading && !preparedPayroll) || isUpdatingPayroll}
256281
payrollBlockers={payrollBlockers}
257282
pagination={pagination}
258283
withReimbursements={withReimbursements}

0 commit comments

Comments
 (0)