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/hooks/HookComponent.tsx b/client/src/hooks/HookComponent.tsx
new file mode 100644
index 0000000..6f89312
--- /dev/null
+++ b/client/src/hooks/HookComponent.tsx
@@ -0,0 +1,62 @@
+/*
+ * 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 { useEffect } from 'react';
+
+export type ConditionalHookProps<T, A> = {
+ useHook: (args: A) => T;
+ args: A;
+ callback?: (value: T) => void;
+ unmountCallback?: (prevValue: T) => void;
+};
+
+/**
+ * Component that, when mounted, calls `useHook` and passes its return value as an argument to the `callback` function.
+ *
+ * @example
+ * // Conditionally call `useMyHook` with arguments `myArgs`
+ * <>
+ * {myCondition && (
+ * <HookComponent
+ * useHook={useMyHook}
+ * args={myArgs}
+ * callback={(v) => console.log(v)}
+ * />
+ * )}
+ * </>
+ *
+ */
+const HookComponent = <T, A>({
+ useHook,
+ args,
+ callback = () => {},
+ unmountCallback = () => {},
+}: ConditionalHookProps<T, A>) => {
+ const value = useHook(args);
+
+ useEffect(() => {
+ callback(value);
+
+ return () => {
+ unmountCallback(value);
+ };
+ }, [value, callback, unmountCallback]);
+
+ return null;
+};
+
+export default HookComponent;