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/WebRtcProvider.tsx b/client/src/contexts/WebRtcProvider.tsx
index bb99717..55a3a1d 100644
--- a/client/src/contexts/WebRtcProvider.tsx
+++ b/client/src/contexts/WebRtcProvider.tsx
@@ -17,20 +17,21 @@
*/
import { WebRtcIceCandidate, WebRtcSdp, WebSocketMessageType } from 'jami-web-common';
-import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
-import LoadingPage from '../components/Loading';
+import { createOptionalContext } from '../hooks/createOptionalContext';
import { Conversation } from '../models/conversation';
import { WithChildren } from '../utils/utils';
import { useAuthContext } from './AuthProvider';
import { CallManagerContext } from './CallManagerProvider';
+import ConditionalContextProvider from './ConditionalContextProvider';
import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
export type MediaDevicesInfo = Record<MediaDeviceKind, MediaDeviceInfo[]>;
export type MediaInputKind = 'audio' | 'video';
export type MediaInputIds = Record<MediaInputKind, string | false | undefined>;
-interface IWebRtcContext {
+export interface IWebRtcContext {
iceConnectionState: RTCIceConnectionState | undefined;
localStream: MediaStream | undefined;
@@ -44,19 +45,8 @@
closeConnection: () => void;
}
-const defaultWebRtcContext: IWebRtcContext = {
- iceConnectionState: undefined,
- localStream: undefined,
- screenShareLocalStream: undefined,
- remoteStreams: undefined,
- getMediaDevices: async () => Promise.reject(),
- updateLocalStream: async () => Promise.reject(),
- updateScreenShare: async () => Promise.reject(),
- sendWebRtcOffer: async () => Promise.reject(),
- closeConnection: () => {},
-};
-
-export const WebRtcContext = createContext<IWebRtcContext>(defaultWebRtcContext);
+const optionalWebRtcContext = createOptionalContext<IWebRtcContext>('WebRtcContext');
+export const useWebRtcContext = optionalWebRtcContext.useOptionalContext;
export default ({ children }: WithChildren) => {
const { account } = useAuthContext();
@@ -65,7 +55,12 @@
const { callConversation, callData } = useContext(CallManagerContext);
useEffect(() => {
- if (!webRtcConnection && account) {
+ if (webRtcConnection && !callData) {
+ setWebRtcConnection(undefined);
+ return;
+ }
+
+ if (!webRtcConnection && account && callData) {
const iceServers: RTCIceServer[] = [];
if (account.details['TURN.enable'] === 'true') {
@@ -84,31 +79,36 @@
setWebRtcConnection(new RTCPeerConnection({ iceServers }));
}
- }, [account, webRtcConnection]);
+ }, [account, webRtcConnection, callData]);
- if (!webRtcConnection || !webSocket || !callConversation || !callData?.conversationId) {
- return <LoadingPage />;
- }
+ const dependencies = useMemo(
+ () => ({
+ webRtcConnection,
+ webSocket,
+ conversation: callConversation,
+ conversationId: callData?.conversationId,
+ }),
+ [webRtcConnection, webSocket, callConversation, callData?.conversationId]
+ );
return (
- <WebRtcProvider
- webRtcConnection={webRtcConnection}
- webSocket={webSocket}
- conversation={callConversation}
- conversationId={callData.conversationId}
+ <ConditionalContextProvider
+ Context={optionalWebRtcContext.Context}
+ initialValue={undefined}
+ dependencies={dependencies}
+ useProviderValue={useWebRtcContextValue}
>
{children}
- </WebRtcProvider>
+ </ConditionalContextProvider>
);
};
-const WebRtcProvider = ({
- children,
+const useWebRtcContextValue = ({
conversation,
conversationId,
webRtcConnection,
webSocket,
-}: WithChildren & {
+}: {
webRtcConnection: RTCPeerConnection;
webSocket: IWebSocketContext;
conversation: Conversation;
@@ -438,21 +438,28 @@
webRtcConnection.close();
}, [webRtcConnection, localStream, screenShareLocalStream]);
- return (
- <WebRtcContext.Provider
- value={{
- iceConnectionState,
- localStream,
- screenShareLocalStream,
- remoteStreams,
- getMediaDevices,
- updateLocalStream,
- updateScreenShare,
- sendWebRtcOffer,
- closeConnection,
- }}
- >
- {children}
- </WebRtcContext.Provider>
+ return useMemo(
+ () => ({
+ iceConnectionState,
+ localStream,
+ screenShareLocalStream,
+ remoteStreams,
+ getMediaDevices,
+ updateLocalStream,
+ updateScreenShare,
+ sendWebRtcOffer,
+ closeConnection,
+ }),
+ [
+ iceConnectionState,
+ localStream,
+ screenShareLocalStream,
+ remoteStreams,
+ getMediaDevices,
+ updateLocalStream,
+ updateScreenShare,
+ sendWebRtcOffer,
+ closeConnection,
+ ]
);
};