From 16033e9f9994033ead0ace2a4ec11690257f7f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:29:42 +0200 Subject: [PATCH] Fix front component worker re-creation on every render (#19245) - `frontComponentHostCommunicationApi` gets a new object reference on every render, causing the `useMemo`/`useEffect` in `FrontComponentWorkerEffect` to tear down and re-create the web worker each time. - Decouple the host API lifecycle from the worker lifecycle by moving thread.exports updates into a dedicated `FrontComponentUpdateHostCommunicationApiEffect` that mutates the thread's exports object in place via Object.assign. - Rename `FrontComponentHostCommunicationApiEffect` to `FrontComponentInitializeHostCommunicationApiEffect` for clarity. ## Before https://github.com/user-attachments/assets/6f3a5c14-2ae7-4317-82b5-1625abb4143e ## After https://github.com/user-attachments/assets/1059d6cd-e02c-4477-b3e8-8e965a716434 --- .../components/FrontComponentRenderer.tsx | 13 ++++++++---- .../src/index.ts | 3 ++- ...tInitializeHostCommunicationApiEffect.tsx} | 6 +++--- ...ponentUpdateHostCommunicationApiEffect.tsx | 20 ++++++++++++++++++ .../components/FrontComponentWorkerEffect.tsx | 21 ++++++++++++++----- 5 files changed, 50 insertions(+), 13 deletions(-) rename packages/twenty-front-component-renderer/src/remote/components/{FrontComponentHostCommunicationApiEffect.tsx => FrontComponentInitializeHostCommunicationApiEffect.tsx} (73%) create mode 100644 packages/twenty-front-component-renderer/src/remote/components/FrontComponentUpdateHostCommunicationApiEffect.tsx diff --git a/packages/twenty-front-component-renderer/src/host/components/FrontComponentRenderer.tsx b/packages/twenty-front-component-renderer/src/host/components/FrontComponentRenderer.tsx index d3c09ec6c2..36f6c26d88 100644 --- a/packages/twenty-front-component-renderer/src/host/components/FrontComponentRenderer.tsx +++ b/packages/twenty-front-component-renderer/src/host/components/FrontComponentRenderer.tsx @@ -1,6 +1,7 @@ import { FrontComponentErrorEffect } from '@/remote/components/FrontComponentErrorEffect'; -import { FrontComponentHostCommunicationApiEffect } from '@/remote/components/FrontComponentHostCommunicationApiEffect'; +import { FrontComponentInitializeHostCommunicationApiEffect } from '@/remote/components/FrontComponentInitializeHostCommunicationApiEffect'; import { FrontComponentUpdateContextEffect } from '@/remote/components/FrontComponentUpdateContextEffect'; +import { FrontComponentUpdateHostCommunicationApiEffect } from '@/remote/components/FrontComponentUpdateHostCommunicationApiEffect'; import { type FrontComponentHostCommunicationApi } from '@/types/FrontComponentHostCommunicationApi'; import { type SdkClientUrls } from '@/types/HostToWorkerRenderContext'; import { type WorkerExports } from '@/types/WorkerExports'; @@ -55,7 +56,6 @@ export const FrontComponentRenderer = ({ apiUrl={apiUrl} sdkClientUrls={sdkClientUrls} frontComponentId={executionContext.frontComponentId} - frontComponentHostCommunicationApi={frontComponentHostCommunicationApi} setReceiver={setReceiver} setThread={setThread} setError={setError} @@ -63,7 +63,6 @@ export const FrontComponentRenderer = ({ ); }, [ componentUrl, - frontComponentHostCommunicationApi, setError, setReceiver, setThread, @@ -102,7 +101,13 @@ export const FrontComponentRenderer = ({ {isDefined(thread) && ( <> - + + ; }; -export const FrontComponentHostCommunicationApiEffect = ({ +export const FrontComponentInitializeHostCommunicationApiEffect = ({ thread, -}: FrontComponentHostCommunicationApiEffectProps) => { +}: FrontComponentInitializeHostCommunicationApiEffectProps) => { useEffect(() => { thread.imports.initializeHostCommunicationApi().catch((error) => { console.error('Failed to initialize host communication API:', error); diff --git a/packages/twenty-front-component-renderer/src/remote/components/FrontComponentUpdateHostCommunicationApiEffect.tsx b/packages/twenty-front-component-renderer/src/remote/components/FrontComponentUpdateHostCommunicationApiEffect.tsx new file mode 100644 index 0000000000..bdc8dd4540 --- /dev/null +++ b/packages/twenty-front-component-renderer/src/remote/components/FrontComponentUpdateHostCommunicationApiEffect.tsx @@ -0,0 +1,20 @@ +import { type FrontComponentHostCommunicationApi } from '@/types/FrontComponentHostCommunicationApi'; +import { type WorkerExports } from '@/types/WorkerExports'; +import { type ThreadWebWorker } from '@quilted/threads'; +import { useEffect } from 'react'; + +type FrontComponentUpdateHostCommunicationApiEffectProps = { + thread: ThreadWebWorker; + frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi; +}; + +export const FrontComponentUpdateHostCommunicationApiEffect = ({ + thread, + frontComponentHostCommunicationApi, +}: FrontComponentUpdateHostCommunicationApiEffectProps) => { + useEffect(() => { + Object.assign(thread.exports, frontComponentHostCommunicationApi); + }, [thread, frontComponentHostCommunicationApi]); + + return null; +}; diff --git a/packages/twenty-front-component-renderer/src/remote/components/FrontComponentWorkerEffect.tsx b/packages/twenty-front-component-renderer/src/remote/components/FrontComponentWorkerEffect.tsx index 7d93f58ddf..2781c128b9 100644 --- a/packages/twenty-front-component-renderer/src/remote/components/FrontComponentWorkerEffect.tsx +++ b/packages/twenty-front-component-renderer/src/remote/components/FrontComponentWorkerEffect.tsx @@ -1,8 +1,8 @@ import { ThreadWebWorker, release, retain } from '@quilted/threads'; import { RemoteReceiver } from '@remote-dom/core/receivers'; import { useEffect, useRef } from 'react'; -import { type ConfirmationModalCaller } from 'twenty-shared/types'; import { type CommandConfirmationModalResult } from 'twenty-sdk'; +import { type ConfirmationModalCaller } from 'twenty-shared/types'; import { type FrontComponentHostCommunicationApi } from '../../types/FrontComponentHostCommunicationApi'; import { type SdkClientUrls } from '../../types/HostToWorkerRenderContext'; import { type WorkerExports } from '../../types/WorkerExports'; @@ -17,13 +17,26 @@ type CommandMenuItemConfirmationModalResultBrowserEventDetail = { confirmationResult: CommandConfirmationModalResult; }; +const noopAsync = async () => {}; + +const HOST_COMMUNICATION_API_NOOP_INITIALIZATION: FrontComponentHostCommunicationApi = + { + navigate: noopAsync, + requestAccessTokenRefresh: async () => '', + openSidePanelPage: noopAsync, + openCommandConfirmationModal: noopAsync, + unmountFrontComponent: noopAsync, + enqueueSnackbar: noopAsync, + closeSidePanel: noopAsync, + updateProgress: noopAsync, + }; + type FrontComponentWorkerEffectProps = { componentUrl: string; applicationAccessToken?: string; apiUrl?: string; sdkClientUrls?: SdkClientUrls; frontComponentId: string; - frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi; setReceiver: React.Dispatch>; setThread: React.Dispatch< React.SetStateAction(worker, { - exports: frontComponentHostCommunicationApi, + exports: { ...HOST_COMMUNICATION_API_NOOP_INITIALIZATION }, }); const handleCommandMenuItemConfirmationModalResultBrowserEvent = ( @@ -135,7 +147,6 @@ export const FrontComponentWorkerEffect = ({ setError, setReceiver, setThread, - frontComponentHostCommunicationApi, ]); return null;