Remove recoil sync (#11569)

Recoil-sync was causing issues with Firefox, replacing it with a simpler
mechanism to hydrate variables on page load

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
This commit is contained in:
Félix Malfait
2025-04-15 13:32:12 +02:00
committed by GitHub
parent 6c2d64dcb2
commit e8db0176a1
17 changed files with 102 additions and 139 deletions

View File

@ -9,35 +9,32 @@ import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { HelmetProvider } from 'react-helmet-async';
import { RecoilRoot } from 'recoil';
import { RecoilURLSyncJSON } from 'recoil-sync';
import { initialI18nActivate } from '~/utils/i18n/initialI18nActivate';
import { IconsProvider } from 'twenty-ui/display';
import { initialI18nActivate } from '~/utils/i18n/initialI18nActivate';
initialI18nActivate();
export const App = () => {
return (
<RecoilRoot>
<RecoilURLSyncJSON location={{ part: 'queryParams' }}>
<AppErrorBoundary
resetOnLocationChange={false}
FallbackComponent={AppRootErrorFallback}
>
<I18nProvider i18n={i18n}>
<RecoilDebugObserverEffect />
<ApolloDevLogEffect />
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<IconsProvider>
<ExceptionHandlerProvider>
<HelmetProvider>
<AppRouter />
</HelmetProvider>
</ExceptionHandlerProvider>
</IconsProvider>
</SnackBarProviderScope>
</I18nProvider>
</AppErrorBoundary>
</RecoilURLSyncJSON>
<AppErrorBoundary
resetOnLocationChange={false}
FallbackComponent={AppRootErrorFallback}
>
<I18nProvider i18n={i18n}>
<RecoilDebugObserverEffect />
<ApolloDevLogEffect />
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<IconsProvider>
<ExceptionHandlerProvider>
<HelmetProvider>
<AppRouter />
</HelmetProvider>
</ExceptionHandlerProvider>
</IconsProvider>
</SnackBarProviderScope>
</I18nProvider>
</AppErrorBoundary>
</RecoilRoot>
);
};

View File

@ -26,6 +26,7 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
import { isDefined } from 'twenty-shared/utils';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
// TODO: break down into smaller functions and / or hooks
// - moved usePageChangeEffectNavigateLocation into dedicated hook
@ -44,6 +45,8 @@ export const PageChangeEffect = () => {
const eventTracker = useEventTracker();
const { initializeQueryParamState } = useInitializeQueryParamState();
//TODO: refactor useResetTableRowSelection hook to not throw when the argument `recordTableId` is an empty string
// - replace CoreObjectNamePlural.Person
const objectNamePlural =
@ -64,10 +67,12 @@ export const PageChangeEffect = () => {
}, [location, previousLocation, executeTasksOnAnyLocationChange]);
useEffect(() => {
initializeQueryParamState();
if (isDefined(pageChangeEffectNavigateLocation)) {
navigate(pageChangeEffectNavigateLocation);
}
}, [navigate, pageChangeEffectNavigateLocation]);
}, [navigate, pageChangeEffectNavigateLocation, initializeQueryParamState]);
useEffect(() => {
const isLeavingRecordIndexPage = !!matchPath(

View File

@ -0,0 +1,66 @@
import { useRecoilCallback } from 'recoil';
import { isQueryParamInitializedState } from '@/app/states/isQueryParamInitializedState';
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
// Initialize state that are hydrated from query parameters
// We used to use recoil-sync to do this, but it was causing issues with Firefox
export const useInitializeQueryParamState = () => {
const initializeQueryParamState = useRecoilCallback(
({ set, snapshot }) =>
() => {
const isInitialized = snapshot
.getLoadable(isQueryParamInitializedState)
.getValue();
if (!isInitialized) {
const handlers = {
billingCheckoutSession: (value: string) => {
try {
const parsedValue = JSON.parse(decodeURIComponent(value));
if (
typeof parsedValue === 'object' &&
parsedValue !== null &&
'plan' in parsedValue &&
'interval' in parsedValue &&
'requirePaymentMethod' in parsedValue
) {
set(
billingCheckoutSessionState,
parsedValue as BillingCheckoutSession,
);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(
'Failed to parse billingCheckoutSession from URL',
error,
);
set(
billingCheckoutSessionState,
BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
);
}
},
};
const queryParams = new URLSearchParams(window.location.search);
for (const [paramName, handler] of Object.entries(handlers)) {
const value = queryParams.get(paramName);
if (value !== null) {
handler(value);
}
}
set(isQueryParamInitializedState, true);
}
},
[],
);
return { initializeQueryParamState };
};

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui/utilities';
export const isQueryParamInitializedState = createState<boolean>({
key: 'isQueryParamInitializedState',
defaultValue: false,
});