Fix components rerendering unnecessarily
Before, some providers (WebRtcProvider, CallProvider...) were rendered conditionally.
This was causing all their children to re-render when the provider rendered.
Instead of using conditional rendering, these providers now use `ConditionalContextProvider`.
It always renders the provider, but sets its value to `initialValue` if the dependencies are not all defined.
If all dependencies are defined, the value is the result of the `useProviderValue` hook.
Changes:
- New file: `ConditionalContextProvider`
- New file: `HookComponent` - Component that calls a hook when mounted and calls a `callback` function with the hook result as argument
- For `WebRtcProvider` and `CallProvider`, use `createOptionalContext` to create the context and `ConditionalContextProvider` to provide their value only when the conditions are met
- In `WebRtcProvider`, set the webRtcConnection to undefined when not in a call
- For all providers, wrap their `value` prop in `useMemo` to avoid unnecessary rerenders
Change-Id: Ide947e216d54599aabc71cf4bd026bd20d6e0daf
diff --git a/client/src/contexts/CallManagerProvider.tsx b/client/src/contexts/CallManagerProvider.tsx
index 9bde186..7d8d843 100644
--- a/client/src/contexts/CallManagerProvider.tsx
+++ b/client/src/contexts/CallManagerProvider.tsx
@@ -16,7 +16,7 @@
* <https://www.gnu.org/licenses/>.
*/
import { CallBegin, WebSocketMessageType } from 'jami-web-common';
-import { createContext, useCallback, useContext, useEffect, useState } from 'react';
+import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { RemoteVideoOverlay } from '../components/VideoOverlay';
@@ -29,7 +29,7 @@
import WebRtcProvider from './WebRtcProvider';
import { WebSocketContext } from './WebSocketProvider';
-type CallData = {
+export type CallData = {
conversationId: string;
role: CallRole;
withVideoOn?: boolean;
@@ -43,15 +43,23 @@
exitCall: () => void;
};
-export const CallManagerContext = createContext<ICallManagerContext>(undefined!);
+const defaultCallManagerContext: ICallManagerContext = {
+ callData: undefined,
+ callConversation: undefined,
+
+ startCall: () => {},
+ exitCall: () => {},
+};
+
+export const CallManagerContext = createContext<ICallManagerContext>(defaultCallManagerContext);
CallManagerContext.displayName = 'CallManagerContext';
export default ({ children }: WithChildren) => {
const [callData, setCallData] = useState<CallData>();
const webSocket = useContext(WebSocketContext);
const navigate = useNavigate();
- const conversationId = callData?.conversationId;
- const { conversation } = useConversationQuery(conversationId);
+ const { conversation } = useConversationQuery(callData?.conversationId);
+ const { urlParams } = useUrlParams<ConversationRouteParams>();
const failStartCall = useCallback(() => {
throw new Error('Cannot start call: Already in a call');
@@ -90,37 +98,26 @@
};
}, [webSocket, navigate, startCall, callData]);
- return (
- <CallManagerContext.Provider
- value={{
- startCall,
- callData,
- callConversation: conversation,
- exitCall,
- }}
- >
- <CallManagerProvider>{children}</CallManagerProvider>
- </CallManagerContext.Provider>
+ const value = useMemo(
+ () => ({
+ startCall,
+ callData,
+ callConversation: conversation,
+ exitCall,
+ }),
+ [startCall, callData, conversation, exitCall]
);
-};
-
-const CallManagerProvider = ({ children }: WithChildren) => {
- const { callData } = useContext(CallManagerContext);
- const { urlParams } = useUrlParams<ConversationRouteParams>();
- const conversationId = urlParams.conversationId;
-
- if (!callData) {
- return <>{children}</>;
- }
return (
- <WebRtcProvider>
- <CallProvider>
- {callData.conversationId !== conversationId && (
- <RemoteVideoOverlay callConversationId={callData.conversationId} />
- )}
- {children}
- </CallProvider>
- </WebRtcProvider>
+ <CallManagerContext.Provider value={value}>
+ <WebRtcProvider>
+ <CallProvider>
+ {callData && callData.conversationId !== urlParams.conversationId && (
+ <RemoteVideoOverlay callConversationId={callData.conversationId} />
+ )}
+ {children}
+ </CallProvider>
+ </WebRtcProvider>
+ </CallManagerContext.Provider>
);
};