diff --git a/src/components/Teams/Team.jsx b/src/components/Teams/Team.jsx index dc817fb51a..ac479edbaa 100644 --- a/src/components/Teams/Team.jsx +++ b/src/components/Teams/Team.jsx @@ -1,6 +1,6 @@ /* eslint-disable react/destructuring-assignment */ import styles from './Team.module.css'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { connect, useSelector, useDispatch } from 'react-redux'; import { Button } from 'reactstrap'; @@ -77,40 +77,31 @@ export function Team({ // string key for cache/DOM ids const teamIdKey = String(props.teamId ?? ''); + // localMembers is only populated after the user opens the modal or hovers. + // On mount we do NOT pre-fetch — that was firing GET /api/team/:id/users + // for every single row on page load (thousands of requests = 30s load time). + // Member counts are computed from props.team.members which already comes + // from the getAllTeams aggregation, so no extra request is needed. const [localMembers, setLocalMembers] = useState(() => getCachedTeamMembers(teamIdKey) || null); - const [loading, setLoading] = useState(false); - - useEffect(() => { - const cached = getCachedTeamMembers(teamIdKey); - if (cached && !localMembers) setLocalMembers(cached); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [teamIdKey]); - - useEffect(() => { - if (!getCachedTeamMembers(teamIdKey) && teamIdKey) { - fetchTeamMembersCached(dispatch, getTeamMembers, teamIdKey) - .then(setLocalMembers) - .catch(() => {}); - } - }, [dispatch, teamIdKey]); const members = localMembers ?? props.team?.members ?? []; - const { total, active: activeCount, inactive } = computeCounts(members, loading, localMembers); + const { total, active: activeCount, inactive } = computeCounts(members, false, localMembers); - // Fire callback immediately (keeps tests & UX snappy), then refresh members + // Fire callback immediately (keeps tests & UX snappy), then refresh members. + // We do NOT reset localMembers to null or set loading=true here — doing so + // caused the count to flash back to '…' on every click. Since props.team.members + // is always available as a fallback, the count stays stable while the fresh + // fetch completes in the background. const handleOpenMembers = () => { clearCachedTeamMembers(teamIdKey); - setLocalMembers(null); // reset stale local state if (typeof props.onMembersClick === 'function') { props.onMembersClick(teamIdRaw, props.name, props.teamCode); // don't pass localMembers } - setLoading(true); fetchTeamMembersCached(dispatch, getTeamMembers, teamIdKey) .then(fresh => { setLocalMembers(fresh); }) - .catch(() => {}) - .finally(() => setLoading(false)); + .catch(() => {}); }; return ( @@ -165,9 +156,8 @@ export function Team({ onClick={handleOpenMembers} data-testid="members-btn" aria-label="Users" - disabled={loading} > - {loading ? : } + diff --git a/src/components/Teams/TeamMembersPopup.jsx b/src/components/Teams/TeamMembersPopup.jsx index 9f747a83cb..e1508d0d65 100644 --- a/src/components/Teams/TeamMembersPopup.jsx +++ b/src/components/Teams/TeamMembersPopup.jsx @@ -158,12 +158,29 @@ export const TeamMembersPopup = React.memo(props => { const map = {}; if (Array.isArray(teamsData) && teamsData.length > 0) { for (const m of teamsData[0]?.members || []) { - map[m.userId] = m.visible; + // Backend's getAllTeams aggregation returns each member object + // keyed by `_id` (the user's id), not `userId`. Using the wrong + // key here meant every lookup below resolved to `undefined`, + // which made the "See All" toggle appear to reset itself. + map[m._id] = m.visible; } } return map; }, [props.teamData]); + // Only render toggle rows once teamData AND its members are populated so that + // `choice` is never undefined on first mount — prevents the brief ON flash. + // props.teamData can exist with members: undefined on the first Redux update, + // so we must check members is a non-empty array too. + const isMemberVisibilityReady = useMemo(() => { + return ( + Array.isArray(props.teamData) && + props.teamData.length > 0 && + Array.isArray(props.teamData[0]?.members) && + props.teamData[0].members.length > 0 + ); + }, [props.teamData]); + useEffect(() => { setIsValidUser(true); setDuplicateUserAlert(false); @@ -303,7 +320,7 @@ export const TeamMembersPopup = React.memo(props => { }; const renderBody = () => { - if (showTableSpinner) { + if (showTableSpinner || !isMemberVisibilityReady) { return (