Skip to content

Commit

Permalink
fix(onboarding): Modify useCurrentProjectState to not require an addi…
Browse files Browse the repository at this point in the history
…tional rerender (#85195)

Am updating some onboarding components and noticed that this hook will
always require one rerender to return the correct project. Small
refactor to find the default project on initial render if the projects
are already available.
  • Loading branch information
malwilley authored Feb 13, 2025
1 parent a1c377e commit ac161e9
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,25 @@ describe('useCurrentProjectState', () => {
act(() => result.current.setCurrentProject(angular));
expect(result.current.currentProject).toBe(angular);
});

it('should update when the page filters store changes', () => {
ProjectsStore.loadInitialData([javascript, angular]);
mockPageFilterStore([angular]);
const {result} = renderHook(useCurrentProjectState, {
initialProps: {
currentPanel: SidebarPanelKey.FEEDBACK_ONBOARDING,
targetPanel: SidebarPanelKey.FEEDBACK_ONBOARDING,
onboardingPlatforms: feedbackOnboardingPlatforms,
allPlatforms: feedbackOnboardingPlatforms,
},
wrapper: createWrapper(),
});

// Starts with angular
expect(result.current.currentProject).toBe(angular);

// Changes to javascript when page filters change
act(() => mockPageFilterStore([javascript]));
expect(result.current.currentProject).toBe(javascript);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useEffect, useMemo, useState} from 'react';
import {useCallback, useEffect, useMemo, useState} from 'react';
import partition from 'lodash/partition';

import type {SidebarPanelKey} from 'sentry/components/sidebar/types';
Expand All @@ -23,53 +23,35 @@ function useCurrentProjectState({
}: Props) {
const {projects, initiallyLoaded: projectsLoaded} = useProjects();
const {selection, isReady} = useLegacyStore(PageFiltersStore);
const [currentProject, setCurrentProject] = useState<Project | undefined>(undefined);
const {getParamValue: projectIds} = useUrlParams('project');
const projectId = projectIds()?.split('&').at(0);
const isActive = currentPanel === targetPanel;

// Projects with onboarding instructions
const projectsWithOnboarding = projects.filter(
p => p.platform && onboardingPlatforms.includes(p.platform)
const projectsWithOnboarding = useMemo(
() => projects.filter(p => p.platform && onboardingPlatforms.includes(p.platform)),
[projects, onboardingPlatforms]
);

const [supportedProjects, unsupportedProjects] = useMemo(() => {
return partition(projects, p => p.platform && allPlatforms.includes(p.platform));
}, [projects, allPlatforms]);

useEffect(() => {
if (!isActive) {
setCurrentProject(undefined);
return;
}

if (
currentProject ||
!projectsLoaded ||
!projects.length ||
!isReady ||
!projectsWithOnboarding ||
!supportedProjects
) {
return;
}

if (projectId) {
setCurrentProject(projects.find(p => p.id === projectId) ?? undefined);
return;
}
const getDefaultCurrentProjectFromSelection = useCallback(
(selectionProjects: number[]): Project | undefined => {
if (!isActive || !selectionProjects.length) {
return undefined;
}

if (selection.projects.length) {
const selectedProjectIds = selection.projects.map(String);
const selectedProjectIds = selectionProjects.map(String);

// If we selected something that has onboarding instructions, pick that first
const projectForOnboarding = projectsWithOnboarding.find(p =>
selectedProjectIds.includes(p.id)
);

if (projectForOnboarding) {
setCurrentProject(projectForOnboarding);
return;
return projectForOnboarding;
}

// If we selected something that supports the product pick that
Expand All @@ -78,30 +60,73 @@ function useCurrentProjectState({
);

if (projectSupportsProduct) {
setCurrentProject(projectSupportsProduct);
return;
return projectSupportsProduct;
}

// Otherwise, just pick the first selected project
const firstSelectedProject = projects.find(p => selectedProjectIds.includes(p.id));
setCurrentProject(firstSelectedProject);
return;
return projects.find(p => selectedProjectIds.includes(p.id));
},
[isActive, projects, projectsWithOnboarding, supportedProjects]
);

const getDefaultCurrentProject = useCallback((): Project | undefined => {
if (!isActive) {
return undefined;
}

if (
!projectsLoaded ||
!projects.length ||
!isReady ||
!projectsWithOnboarding ||
!supportedProjects
) {
return undefined;
}

if (projectId) {
return projects.find(p => p.id === projectId);
}
// No selection, so pick the first project with onboarding
setCurrentProject(projectsWithOnboarding.at(0) || supportedProjects.at(0));
return;

return (
getDefaultCurrentProjectFromSelection(selection.projects) ??
projectsWithOnboarding.at(0) ??
supportedProjects.at(0)
);
}, [
currentProject,
projectsLoaded,
projects,
isReady,
getDefaultCurrentProjectFromSelection,
isActive,
selection.projects,
isReady,
projectId,
projects,
projectsLoaded,
projectsWithOnboarding,
selection.projects,
supportedProjects,
projectId,
]);

const defaultCurrentProject = getDefaultCurrentProject();

const [currentProject, setCurrentProject] = useState<Project | undefined>(
defaultCurrentProject
);

// Update default project if none is set
useEffect(() => {
if (!isActive) {
return;
}
setCurrentProject(oldProject => oldProject ?? defaultCurrentProject);
}, [setCurrentProject, defaultCurrentProject, isActive]);

// Update the current project when the page filters store changes
useEffect(() => {
const newSelectionProject = getDefaultCurrentProjectFromSelection(selection.projects);
if (newSelectionProject) {
setCurrentProject(newSelectionProject);
}
}, [selection.projects, getDefaultCurrentProjectFromSelection]);

return {
projects: supportedProjects,
allProjects: projects,
Expand Down

0 comments on commit ac161e9

Please sign in to comment.