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/ConditionalContextProvider.tsx b/client/src/contexts/ConditionalContextProvider.tsx
new file mode 100644
index 0000000..3c47cef
--- /dev/null
+++ b/client/src/contexts/ConditionalContextProvider.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Context, useCallback, useState } from 'react';
+
+import HookComponent from '../hooks/HookComponent';
+import { isRequired, PartialNotOptional, WithChildren } from '../utils/utils';
+
+export type ConditionalContextProviderProps<T, B extends object> = WithChildren & {
+ Context: Context<T>;
+ initialValue: T;
+
+ /**
+ * Object containing the values used to get the provider value with `useProviderValue`.
+ * If one or more field is undefined, the hook will not be called and `initialValue` will be used for the provider
+ * value.
+ *
+ * Should be wrapped in `useMemo` to avoid unnecessary hook calls.
+ */
+ dependencies: PartialNotOptional<B>;
+ useProviderValue: (dependencies: B) => T;
+};
+
+/**
+ * A context provider with `initialValue` as its value if not all props of the dependencies object are defined.
+ * If all props are defined, the provider value is the result of `useProviderValue`.
+ */
+const ConditionalContextProvider = <T, B extends object>({
+ Context,
+ initialValue,
+ dependencies,
+ useProviderValue,
+ children,
+}: ConditionalContextProviderProps<T, B>) => {
+ const [value, setValue] = useState(initialValue);
+ const unmountCallback = useCallback(() => setValue(initialValue), [initialValue]);
+
+ return (
+ <>
+ {isRequired(dependencies) && (
+ <HookComponent
+ callback={setValue}
+ unmountCallback={unmountCallback}
+ useHook={useProviderValue}
+ args={dependencies}
+ />
+ )}
+ <Context.Provider value={value}>{children}</Context.Provider>
+ </>
+ );
+};
+
+export default ConditionalContextProvider;