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;