diff --git a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts index d4a54359..87f4a75f 100644 --- a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts +++ b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts @@ -11,13 +11,14 @@ import { memberManagementQueryKeys, OrganizationDetailsMappers, } from '@auth0/universal-components-core'; -import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import React from 'react'; import { showToast } from '@/components/auth0/shared/toast'; import { useCoreClient } from '@/hooks/shared/use-core-client'; import { useErrorHandler } from '@/hooks/shared/use-error-handler'; import { useTranslator } from '@/hooks/shared/use-translator'; +import { getPreviousDataOption } from '@/lib/utils/tanstack-compat'; import type { CreateInvitationInput } from '@/types/my-organization/member-management/organization-invitation-table-types'; import type { UseMemberManagementServiceOptions, @@ -25,6 +26,8 @@ import type { MemberManagementSortConfig, } from '@/types/my-organization/member-management/organization-member-management-types'; +const keepPreviousDataOption = getPreviousDataOption(); + const INVITATION_SORT_FIELD_MAP: Record = { created_at: 'created_at', }; @@ -118,7 +121,7 @@ export function useMemberManagementService( return { invitations, next }; }, enabled: !!coreClient && isInvitationsTabActive && !!invitationParams, - placeholderData: keepPreviousData, + ...keepPreviousDataOption, }); const membersQuery = useQuery({ @@ -137,7 +140,7 @@ export function useMemberManagementService( return { members, next }; }, enabled: !!coreClient && !isInvitationsTabActive && !!memberParams, - placeholderData: keepPreviousData, + ...keepPreviousDataOption, }); const organizationQuery = useQuery({ diff --git a/packages/react/src/hooks/my-organization/use-member-detail.ts b/packages/react/src/hooks/my-organization/use-member-detail.ts index 4c1aed7f..7f4d4ad9 100644 --- a/packages/react/src/hooks/my-organization/use-member-detail.ts +++ b/packages/react/src/hooks/my-organization/use-member-detail.ts @@ -7,6 +7,7 @@ import type { Role } from '@auth0/universal-components-core'; import * as React from 'react'; import { useMemberDetailService } from '@/hooks/my-organization/shared/services/use-member-detail-service'; +import { isMutationLoading } from '@/lib/utils/tanstack-compat'; import type { MemberDetailModalState, MemberDetailTab, @@ -124,10 +125,10 @@ export function useOrganizationMemberDetail( isFetchingMemberRoles: memberRolesQuery.isLoading, isFetchingAvailableRoles: rolesQuery.isLoading || rolesQuery.isFetching, isLoading: memberQuery.isLoading, - isRemovingFromOrg: removeFromOrgMutation.isPending, - isAssigningRoles: assignRolesMutation.isPending, - isRemovingRoles: removeRolesMutation.isPending, - removingRoleIds: removeRolesMutation.isPending ? removingRoles.map((r) => r.id) : [], + isRemovingFromOrg: isMutationLoading(removeFromOrgMutation), + isAssigningRoles: isMutationLoading(assignRolesMutation), + isRemovingRoles: isMutationLoading(removeRolesMutation), + removingRoleIds: isMutationLoading(removeRolesMutation) ? removingRoles.map((r) => r.id) : [], modalState, setActiveTab, diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts index e0d37293..256023d5 100644 --- a/packages/react/src/hooks/my-organization/use-organization-member-management.ts +++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts @@ -10,6 +10,7 @@ import { showToast } from '@/components/auth0/shared/toast'; import { useMemberManagementService } from '@/hooks/my-organization/shared/services/use-member-management-service'; import { useCheckpointPagination } from '@/hooks/shared/use-checkpoint-pagination'; import { useTranslator } from '@/hooks/shared/use-translator'; +import { isMutationLoading } from '@/lib/utils/tanstack-compat'; import type { CreateInvitationInput, IdentityProviderOption, @@ -270,13 +271,13 @@ export function useOrganizationMemberManagement( orgDisplayName: orgDisplayName, isInitialLoading: invitationsQuery.isLoading || membersQuery.isLoading, isFetchingInvitations: invitationsQuery.isFetching, + isCreatingInvitation: isMutationLoading(createInvitationMutation), + isRevokingInvitation: isMutationLoading(revokeInvitationMutation), + isResendingInvitation: isMutationLoading(resendInvitationMutation), isFetchingMembers: membersQuery.isFetching, isFetchingAvailableRoles: rolesQuery.isLoading || rolesQuery.isFetching, - isRemovingFromOrg: removeFromOrgMutation.isPending, - isAssigningRoles: assignRolesMutation.isPending, - isCreatingInvitation: createInvitationMutation.isPending, - isRevokingInvitation: revokeInvitationMutation.isPending, - isResendingInvitation: resendInvitationMutation.isPending, + isRemovingFromOrg: isMutationLoading(removeFromOrgMutation), + isAssigningRoles: isMutationLoading(assignRolesMutation), invitationPagination: { pageSize: invitationPageSize, currentPage: invitationCurrentPage, diff --git a/packages/react/src/lib/utils/tanstack-compat.ts b/packages/react/src/lib/utils/tanstack-compat.ts new file mode 100644 index 00000000..b06b2fc7 --- /dev/null +++ b/packages/react/src/lib/utils/tanstack-compat.ts @@ -0,0 +1,36 @@ +/** + * TanStack Query v4/v5 compatibility utilities. + * @module tanstack-compat + * @internal + */ + +import type { UseMutationResult } from '@tanstack/react-query'; +import { keepPreviousData } from '@tanstack/react-query'; + +/** + * Returns whether a mutation is in a pending/loading state, compatible with both v4 and v5. + * In v5, `isPending` replaces `isLoading` on mutations. + * @param mutation - The mutation result object. + * @returns `true` if the mutation is pending or loading. + */ +export function isMutationLoading< + T extends Pick & { isLoading?: boolean }, +>(mutation: T): boolean { + return mutation.isPending ?? mutation.isLoading ?? false; +} + +type PlaceholderDataOption = { placeholderData: typeof keepPreviousData }; +type LegacyKeepPreviousOption = { keepPreviousData: true }; +type PreviousDataOption = PlaceholderDataOption | LegacyKeepPreviousOption; + +/** + * Returns the correct query option for keeping previous data, compatible with both v4 and v5. + * In v5, `keepPreviousData` boolean option was replaced by `placeholderData: keepPreviousData`. + * @returns The appropriate option object to spread into a query config. + */ +export function getPreviousDataOption(): PreviousDataOption { + if (typeof keepPreviousData === 'function') { + return { placeholderData: keepPreviousData }; + } + return { keepPreviousData: true }; +}