Skip to content

Commit aada4fd

Browse files
committed
better re-rendering
1 parent 54c39f3 commit aada4fd

2 files changed

Lines changed: 55 additions & 48 deletions

File tree

src/components/ChatWidget/ChatWidget.tsx

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useMemo, useCallback } from 'react';
1+
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
22
import type { ChatWidgetProps, DeploymentConfig } from '../../types';
33
import { useChat } from '../../hooks/useChat';
44
import { createAPIClient } from '../../api/client';
@@ -7,7 +7,7 @@ import { ChatContainer } from '../ChatContainer';
77
import { ChatToggleButton } from '../ChatToggleButton';
88
import { ErrorState } from '../ErrorState';
99
import styles from './ChatWidget.module.css';
10-
import { DEFAULT_ENGINE_URL } from '../../api/client';
10+
import { DEFAULT_ENGINE_URL } from '../../api/client';
1111

1212
type ErrorType = 'not_found' | 'network' | 'unknown' | null;
1313

@@ -20,6 +20,7 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
2020
defaultOpen = false,
2121
primaryColor,
2222
agentName,
23+
agentLogoUrl,
2324
welcomeMessage,
2425
showBranding,
2526
className,
@@ -29,41 +30,38 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
2930
onError,
3031
}) => {
3132
const [isOpen, setIsOpen] = useState(defaultOpen);
32-
const [config, setConfig] = useState<DeploymentConfig | null>(null);
33+
const [baseConfig, setBaseConfig] = useState<DeploymentConfig | null>(null);
3334
const [isLoadingConfig, setIsLoadingConfig] = useState(true);
3435
const [errorType, setErrorType] = useState<ErrorType>(null);
3536
const [errorMessage, setErrorMessage] = useState<string | undefined>();
37+
38+
// Track if initial config has been loaded to prevent re-renders
39+
const hasLoadedConfig = useRef(false);
3640

37-
// Create API client based on mode
41+
// Create API client based on mode - NOT dependent on styling props
3842
const apiClient = useMemo(() => {
3943
if (mockMode) {
40-
return createMockAPIClient(
41-
{ primaryColor, agentName, welcomeMessage },
42-
mockResponses
43-
);
44+
// Pass empty config to mock client - we'll apply overrides via effectiveConfig
45+
return createMockAPIClient({}, mockResponses);
4446
}
4547
return createAPIClient(apiBaseUrl);
46-
}, [mockMode, primaryColor, agentName, welcomeMessage, mockResponses, apiBaseUrl]);
48+
}, [mockMode, mockResponses, apiBaseUrl]);
4749

48-
// Fetch deployment config
50+
// Fetch deployment config - only when embedId or mockMode changes
4951
const fetchConfig = useCallback(async () => {
52+
// Skip if already loaded and not a forced refresh
53+
if (hasLoadedConfig.current && baseConfig) {
54+
return;
55+
}
56+
5057
setIsLoadingConfig(true);
5158
setErrorType(null);
5259
setErrorMessage(undefined);
5360

5461
try {
5562
const deploymentConfig = await apiClient.getDeploymentConfig(embedId);
56-
setConfig({
57-
...deploymentConfig,
58-
// Allow prop overrides
59-
primaryColor: primaryColor ?? deploymentConfig.primaryColor,
60-
agentName: agentName ?? deploymentConfig.agentName,
61-
welcomeMessage: welcomeMessage ?? deploymentConfig.welcomeMessage,
62-
styling: {
63-
...(deploymentConfig.styling ?? {}),
64-
...(showBranding !== undefined ? { showBranding } : {}),
65-
},
66-
});
63+
setBaseConfig(deploymentConfig);
64+
hasLoadedConfig.current = true;
6765
} catch (err) {
6866
const error = err instanceof Error ? err : new Error('Failed to load config');
6967

@@ -79,50 +77,56 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
7977

8078
// In mock mode, use default config on error
8179
if (mockMode) {
82-
setConfig({
80+
setBaseConfig({
8381
embedId,
8482
deploymentId: 'mock-deployment',
8583
workerId: 'mock-worker',
8684
flowId: 'mock-flow',
87-
primaryColor: primaryColor ?? '#1a1a2e',
88-
agentName: agentName ?? 'AI Assistant',
89-
styling: showBranding !== undefined ? { showBranding } : undefined,
85+
primaryColor: '#1a1a2e',
86+
agentName: 'AI Assistant',
87+
styling: {},
9088
});
9189
setErrorType(null);
90+
hasLoadedConfig.current = true;
9291
}
9392

9493
onError?.(error);
9594
} finally {
9695
setIsLoadingConfig(false);
9796
}
98-
}, [embedId, mockMode, apiClient, primaryColor, agentName, welcomeMessage, onError]);
97+
}, [embedId, mockMode, apiClient, onError, baseConfig]);
9998

99+
// Only fetch on mount or when embedId changes
100100
useEffect(() => {
101+
hasLoadedConfig.current = false;
101102
fetchConfig();
102-
}, [fetchConfig]);
103+
}, [embedId, mockMode, apiClient]);
103104

104-
// Create a stable config for useChat
105-
const chatConfig = useMemo((): DeploymentConfig => {
106-
if (config) return config;
107-
return {
105+
// Effective config: base config + prop overrides (updates instantly without re-fetch)
106+
const effectiveConfig = useMemo((): DeploymentConfig => {
107+
const base = baseConfig ?? {
108108
embedId,
109109
deploymentId: '',
110110
workerId: '',
111111
flowId: '',
112-
primaryColor,
113-
agentName,
114-
welcomeMessage,
115-
styling:
116-
showBranding !== undefined
117-
? {
118-
showBranding,
119-
}
120-
: undefined,
121112
};
122-
}, [config, embedId, primaryColor, agentName, welcomeMessage, showBranding]);
113+
114+
return {
115+
...base,
116+
// Apply prop overrides - these update instantly without triggering re-fetch
117+
primaryColor: primaryColor ?? base.primaryColor ?? '#1a1a2e',
118+
agentName: agentName ?? base.agentName,
119+
agentLogoUrl: agentLogoUrl ?? base.agentLogoUrl,
120+
welcomeMessage: welcomeMessage ?? base.welcomeMessage,
121+
styling: {
122+
...(base.styling ?? {}),
123+
...(showBranding !== undefined ? { showBranding } : {}),
124+
},
125+
};
126+
}, [baseConfig, embedId, primaryColor, agentName, agentLogoUrl, welcomeMessage, showBranding]);
123127

124128
const chat = useChat({
125-
config: chatConfig,
129+
config: effectiveConfig,
126130
apiClient,
127131
mockMode,
128132
onSessionStart,
@@ -131,13 +135,13 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
131135
onError,
132136
});
133137

134-
// CSS custom properties for theming
138+
// CSS custom properties for theming - uses effectiveConfig for instant updates
135139
const themeStyle = useMemo(
136140
() =>
137141
({
138-
'--bb-primary-color': config?.primaryColor ?? primaryColor ?? '#1a1a2e',
142+
'--bb-primary-color': effectiveConfig.primaryColor ?? '#1a1a2e',
139143
}) as React.CSSProperties,
140-
[config?.primaryColor, primaryColor]
144+
[effectiveConfig.primaryColor]
141145
);
142146

143147
// Handle new chat
@@ -202,7 +206,7 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
202206
>
203207
{isOpen || isInline ? (
204208
<ChatContainer
205-
config={chatConfig}
209+
config={effectiveConfig}
206210
messages={chat.messages}
207211
// toolCalls={chat.toolCalls} // Disabled tool running UI
208212
isLoading={chat.isLoading}
@@ -213,8 +217,8 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({
213217
) : (
214218
<ChatToggleButton
215219
onClick={() => setIsOpen(true)}
216-
agentName={config?.agentName}
217-
agentLogoUrl={config?.agentLogoUrl}
220+
agentName={effectiveConfig.agentName}
221+
agentLogoUrl={effectiveConfig.agentLogoUrl}
218222
/>
219223
)}
220224
</div>

src/types/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export interface ChatWidgetProps {
8787
/** Override agent name */
8888
agentName?: string;
8989

90+
/** Override agent logo URL */
91+
agentLogoUrl?: string;
92+
9093
/** Override welcome message */
9194
welcomeMessage?: string;
9295

0 commit comments

Comments
 (0)