From 579fdef66fe9776b6c60c927de2ccd8e70d7ef09 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Wed, 15 Apr 2026 18:28:14 +0200 Subject: [PATCH 1/2] ref(tsc): projectTeams endpoint to apiOptions --- static/app/actionCreators/projects.tsx | 63 ++++++++----------- .../views/settings/project/projectTeams.tsx | 24 +++---- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/static/app/actionCreators/projects.tsx b/static/app/actionCreators/projects.tsx index f4d333b0c69e83..b412e3de79385d 100644 --- a/static/app/actionCreators/projects.tsx +++ b/static/app/actionCreators/projects.tsx @@ -1,4 +1,5 @@ import {useCallback} from 'react'; +import {queryOptions, skipToken, useQueryClient} from '@tanstack/react-query'; import type {Query} from 'history'; import chunk from 'lodash/chunk'; import debounce from 'lodash/debounce'; @@ -14,9 +15,7 @@ import {ProjectsStatsStore} from 'sentry/stores/projectsStatsStore'; import {ProjectsStore} from 'sentry/stores/projectsStore'; import type {Team} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; -import {getApiUrl} from 'sentry/utils/api/getApiUrl'; -import type {ApiQueryKey} from 'sentry/utils/queryClient'; -import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; +import {apiOptions} from 'sentry/utils/api/apiOptions'; import {useApi} from 'sentry/utils/useApi'; type UpdateParams = { @@ -284,27 +283,7 @@ export function fetchProjectsCount(api: Client, orgSlug: string) { return api.requestPromise(`/organizations/${orgSlug}/projects-count/`); } -function makeProjectTeamsQueryKey({ - orgSlug, - projectSlug, - cursor, -}: { - orgSlug: string; - projectSlug: string; - cursor?: string; -}): ApiQueryKey { - return [ - getApiUrl('/projects/$organizationIdOrSlug/$projectIdOrSlug/teams/', { - path: { - organizationIdOrSlug: orgSlug, - projectIdOrSlug: projectSlug, - }, - }), - {query: {cursor}}, - ]; -} - -export function useFetchProjectTeams({ +export function projectTeamsApiOptions({ orgSlug, projectSlug, cursor, @@ -313,10 +292,19 @@ export function useFetchProjectTeams({ projectSlug: string; cursor?: string; }) { - return useApiQuery(makeProjectTeamsQueryKey({orgSlug, projectSlug, cursor}), { - staleTime: 0, + return queryOptions({ + ...apiOptions.as()( + '/projects/$organizationIdOrSlug/$projectIdOrSlug/teams/', + { + path: + orgSlug && projectSlug + ? {organizationIdOrSlug: orgSlug, projectIdOrSlug: projectSlug} + : skipToken, + query: {cursor}, + staleTime: 0, + } + ), retry: false, - enabled: Boolean(orgSlug && projectSlug), }); } @@ -331,18 +319,17 @@ export function useAddTeamToProject({ }) { const api = useApi(); const queryClient = useQueryClient(); + const {queryKey} = projectTeamsApiOptions({orgSlug, projectSlug, cursor}); return useCallback( async (team: Team) => { await addTeamToProject(api, orgSlug, projectSlug, team); - setApiQueryData( - queryClient, - makeProjectTeamsQueryKey({orgSlug, projectSlug, cursor}), - prevData => (Array.isArray(prevData) ? [team, ...prevData] : [team]) + queryClient.setQueryData(queryKey, prevData => + prevData ? {...prevData, json: [team, ...prevData.json]} : prevData ); }, - [api, orgSlug, projectSlug, cursor, queryClient] + [api, orgSlug, projectSlug, queryKey, queryClient] ); } @@ -357,18 +344,18 @@ export function useRemoveTeamFromProject({ }) { const api = useApi(); const queryClient = useQueryClient(); + const {queryKey} = projectTeamsApiOptions({orgSlug, projectSlug, cursor}); return useCallback( async (teamSlug: string) => { await removeTeamFromProject(api, orgSlug, projectSlug, teamSlug); - setApiQueryData( - queryClient, - makeProjectTeamsQueryKey({orgSlug, projectSlug, cursor}), - prevData => - Array.isArray(prevData) ? prevData.filter(team => team?.slug !== teamSlug) : [] + queryClient.setQueryData(queryKey, prevData => + prevData + ? {...prevData, json: prevData.json.filter(team => team?.slug !== teamSlug)} + : prevData ); }, - [api, orgSlug, projectSlug, cursor, queryClient] + [api, orgSlug, projectSlug, queryKey, queryClient] ); } diff --git a/static/app/views/settings/project/projectTeams.tsx b/static/app/views/settings/project/projectTeams.tsx index f99534e101a228..9d7b492b9e8512 100644 --- a/static/app/views/settings/project/projectTeams.tsx +++ b/static/app/views/settings/project/projectTeams.tsx @@ -1,7 +1,9 @@ +import {useQuery} from '@tanstack/react-query'; + import {addErrorMessage} from 'sentry/actionCreators/indicator'; import { + projectTeamsApiOptions, useAddTeamToProject, - useFetchProjectTeams, useRemoveTeamFromProject, } from 'sentry/actionCreators/projects'; import {hasEveryAccess} from 'sentry/components/acl/access'; @@ -12,6 +14,7 @@ import {Pagination} from 'sentry/components/pagination'; import {SentryDocumentTitle} from 'sentry/components/sentryDocumentTitle'; import {t, tct} from 'sentry/locale'; import {TeamStore} from 'sentry/stores/teamStore'; +import {selectJsonWithHeaders} from 'sentry/utils/api/apiOptions'; import {decodeScalar} from 'sentry/utils/queryString'; import {routeTitleGen} from 'sentry/utils/routeTitle'; import {useLocation} from 'sentry/utils/useLocation'; @@ -29,16 +32,15 @@ export default function ProjectTeams() { const organization = useOrganization(); const {project} = useProjectSettingsOutlet(); - const { - data: projectTeams, - isPending, - isError, - getResponseHeader, - } = useFetchProjectTeams({ - orgSlug: organization.slug, - projectSlug: project.slug, - cursor: decodeScalar(location.query.cursor), + const {data, isPending, isError} = useQuery({ + ...projectTeamsApiOptions({ + orgSlug: organization.slug, + projectSlug: project.slug, + cursor: decodeScalar(location.query.cursor), + }), + select: selectJsonWithHeaders, }); + const projectTeams = data?.json; const handleAddTeamToProject = useAddTeamToProject({ orgSlug: organization.slug, @@ -107,7 +109,7 @@ export default function ProjectTeams() { onRemoveTeam={handleRemoveTeamFromProject} onCreateTeam={handleAddTeamToProject} /> - + ); From 0f6b6673970421d36838e11e3adc6581b8c28fd4 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Wed, 15 Apr 2026 19:08:10 +0200 Subject: [PATCH 2/2] remove useCallback --- static/app/actionCreators/projects.tsx | 37 +++++++++++--------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/static/app/actionCreators/projects.tsx b/static/app/actionCreators/projects.tsx index b412e3de79385d..8fa48a37edcb69 100644 --- a/static/app/actionCreators/projects.tsx +++ b/static/app/actionCreators/projects.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import {queryOptions, skipToken, useQueryClient} from '@tanstack/react-query'; import type {Query} from 'history'; import chunk from 'lodash/chunk'; @@ -321,16 +320,13 @@ export function useAddTeamToProject({ const queryClient = useQueryClient(); const {queryKey} = projectTeamsApiOptions({orgSlug, projectSlug, cursor}); - return useCallback( - async (team: Team) => { - await addTeamToProject(api, orgSlug, projectSlug, team); + return async (team: Team) => { + await addTeamToProject(api, orgSlug, projectSlug, team); - queryClient.setQueryData(queryKey, prevData => - prevData ? {...prevData, json: [team, ...prevData.json]} : prevData - ); - }, - [api, orgSlug, projectSlug, queryKey, queryClient] - ); + queryClient.setQueryData(queryKey, prevData => + prevData ? {...prevData, json: [team, ...prevData.json]} : prevData + ); + }; } export function useRemoveTeamFromProject({ @@ -346,16 +342,13 @@ export function useRemoveTeamFromProject({ const queryClient = useQueryClient(); const {queryKey} = projectTeamsApiOptions({orgSlug, projectSlug, cursor}); - return useCallback( - async (teamSlug: string) => { - await removeTeamFromProject(api, orgSlug, projectSlug, teamSlug); - - queryClient.setQueryData(queryKey, prevData => - prevData - ? {...prevData, json: prevData.json.filter(team => team?.slug !== teamSlug)} - : prevData - ); - }, - [api, orgSlug, projectSlug, queryKey, queryClient] - ); + return async (teamSlug: string) => { + await removeTeamFromProject(api, orgSlug, projectSlug, teamSlug); + + queryClient.setQueryData(queryKey, prevData => + prevData + ? {...prevData, json: prevData.json.filter(team => team?.slug !== teamSlug)} + : prevData + ); + }; }