From f95965d925e35f8079ea3ad5f9ac6a81066defb8 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 3 Apr 2025 14:09:06 -0400 Subject: [PATCH 1/2] Get rid of risky useEffect in restart onboarding flow In certain cases this would cause an infinite loop in the web version of the app. We should eliminate all uses of this "fire and listen with a useEffect" antipattern in the code base, it was a mistake I introduced. This uses the XState `waitFor` function to wait **only once** for the transition to complete, with some error handling in case it fails. --- src/components/Settings/AllSettingsFields.tsx | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index 32d4d1c62d9..6fdda0be817 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -1,7 +1,6 @@ -import { useSelector } from '@xstate/react' import decamelize from 'decamelize' import type { ForwardedRef } from 'react' -import { forwardRef, useEffect, useMemo } from 'react' +import { forwardRef, useMemo } from 'react' import toast from 'react-hot-toast' import { useLocation, useNavigate } from 'react-router-dom' import { Fragment } from 'react/jsx-runtime' @@ -31,6 +30,7 @@ import { reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' import { settingsActor, useSettings } from '@src/machines/appMachine' import { APP_VERSION, IS_NIGHTLY, getReleaseUrl } from '@src/routes/utils' +import { waitFor } from 'xstate' interface AllSettingsFieldsProps { searchParamTab: SettingsLevel @@ -65,44 +65,25 @@ export const AllSettingsFields = forwardRef( return projectPath }, [location.pathname]) - function restartOnboarding() { + async function restartOnboarding() { settingsActor.send({ type: `set.app.onboardingStatus`, data: { level: 'user', value: '' }, }) - } + await waitFor(settingsActor, (s) => s.matches('idle'), { + timeout: 10_000, + }).catch(reportRejection) - /** - * A "listener" for the XState to return to "idle" state - * when the user resets the onboarding, using the callback above - */ - const isSettingsMachineIdle = useSelector(settingsActor, (s) => - s.matches('idle') - ) - useEffect(() => { - async function navigateToOnboardingStart() { - if ( - context.app.onboardingStatus.current === '' && - isSettingsMachineIdle - ) { - if (isFileSettings) { - // If we're in a project, first navigate to the onboarding start here - // so we can trigger the warning screen if necessary - navigate(dotDotSlash(1) + PATHS.ONBOARDING.INDEX) - } else { - // If we're in the global settings, create a new project and navigate - // to the onboarding start in that project - await createAndOpenNewTutorialProject({ onProjectOpen, navigate }) - } - } + if (isFileSettings) { + // If we're in a project, first navigate to the onboarding start here + // so we can trigger the warning screen if necessary + navigate(dotDotSlash(1) + PATHS.ONBOARDING.INDEX) + } else { + // If we're in the global settings, create a new project and navigate + // to the onboarding start in that project + await createAndOpenNewTutorialProject({ onProjectOpen, navigate }) } - navigateToOnboardingStart().catch(reportRejection) - }, [ - isFileSettings, - navigate, - isSettingsMachineIdle, - context.app.onboardingStatus.current, - ]) + } return (
From 48a3b8fa9cb0220c7dc4d1dde443c91071462a9c Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Fri, 4 Apr 2025 09:31:25 -0400 Subject: [PATCH 2/2] Fix lint by wrapping in synchronous function --- src/components/Settings/AllSettingsFields.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index 6fdda0be817..7eda78850aa 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -166,7 +166,9 @@ export const AllSettingsFields = forwardRef( > { + restartOnboarding().catch(reportRejection) + }} iconStart={{ icon: 'refresh', size: 'sm',