-
Notifications
You must be signed in to change notification settings - Fork 63
Description
Problem
ElevenLabsProvider contains 8+ pieces of frequently-changing state, causing all children to rerender on every state change. This is a fundamental architectural issue that can't be fully fixed with memoization alone. I tried to fix it in #340 but it's still not a full fix.
Unlike other React Native libraries (see PostHog, React Query, Redux), ElevenLabsProvider puts call state inside the provider component, which causes performance issues.
Current Architecture (Problematic)
export const ElevenLabsProvider = ({ children }) => {
// ❌ These state changes cause provider to rerender
const [token, setToken] = useState('');
const [connect, setConnect] = useState(false);
const [status, setStatus] = useState('disconnected'); // Changes during call
const [conversationId, setConversationId] = useState('');
const [isSpeaking, setIsSpeaking] = useState(false); // Toggles constantly
const [canSendFeedback, setCanSendFeedback] = useState(false);
const [serverUrl, setServerUrl] = useState(DEFAULT_SERVER_URL);
const [tokenFetchUrl, setTokenFetchUrl] = useState(undefined);
// When any state changes, provider rerenders → all children rerender
return (
<ElevenLabsContext.Provider value={contextValue}>
<LiveKitRoomWrapper {...props}>
{children} {/* ← Rerenders on every state change */}
</LiveKitRoomWrapper>
</ElevenLabsContext.Provider>
);
};Every setState call causes the provider to rerender, which causes all children to rerender by default in React.
Example: PostHog's Approach (Better)
PostHog's PostHogProvider can wrap an entire app without performance issues because it doesn't have internal state:
// PostHog's approach (simplified)
export function PostHogProvider({ children, apiKey, options }) {
// ✅ Creates client ONCE on mount - stable reference
const client = useMemo(() => new PostHog(apiKey, options), [apiKey]);
// ✅ Context value is stable - doesn't change during usage
const value = useMemo(() => ({ client }), [client]);
// ✅ No state = no rerenders = wrapping entire app is fine
return (
<PostHogContext.Provider value={value}>
{children}
</PostHogContext.Provider>
);
}PostHog can wrap an entire app because the provider never rerenders.
Proposed Architecture (Following PostHog's Pattern)
// Provider only provides stable client instance (no state)
export function ElevenLabsProvider({ children }) {
// ✅ Client is stable - doesn't change
const client = useMemo(() => new ElevenLabsClient(), []);
return (
<ElevenLabsContext.Provider value={client}>
{children}
</ElevenLabsContext.Provider>
);
}
// State managed by consumers via hooks
export function useConversation(options) {
const client = useContext(ElevenLabsContext);
// ✅ State is local to the component using the hook
const [status, setStatus] = useState('disconnected');
const [isSpeaking, setIsSpeaking] = useState(false);
const [canSendFeedback, setCanSendFeedback] = useState(false);
// ✅ Only components using this hook rerender when state changes
useEffect(() => {
// Setup LiveKit connection using client
// Update local state as needed
}, [client]);
return {
status,
isSpeaking,
canSendFeedback,
startSession,
endSession,
// ...
};
}Benefits
- ✅ Provider never rerenders (no state changes)
- ✅ Only components using the hook manage state (isolated rerenders)
- ✅ Can wrap entire app without performance concerns
- ✅ Better separation of concerns (provider = client, hook = state)
- ✅ Follows React best practices and patterns used by successful libraries
Comparison to Other Libraries
| Library | Provider Has State? | Can Wrap Entire App? |
|---|---|---|
| PostHog | ❌ No | ✅ Yes |
| React Query | ❌ No | ✅ Yes |
| Redux | ❌ No | ✅ Yes |
| ElevenLabs | ✅ Yes (8+ pieces) | ❌ No (performance issues) |
Current Workarounds
Developers currently have to:
- Only wrap minimal UI components with
ElevenLabsProvider(can't wrap entire app) - Use
React.memoon direct children to prevent rerenders - Apply patches to memoize context values (see PR Prevent Unnecessary Rerenders in ElevenLabsProvider (React Native) #340)
References
- PostHog React Native: https://posthog.com/docs/libraries/react-native
- React Context Performance: https://react.dev/reference/react/useMemo#skipping-re-rendering-of-components
- React Query Architecture: https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr
Would love to see ElevenLabs adopt this pattern - it would make the library much more ergonomic and performant! 🚀