aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/hoc/withInternalFallback.tsx
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/components/hoc/withInternalFallback.tsx
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/hoc/withInternalFallback.tsx')
-rw-r--r--packages/excalidraw/components/hoc/withInternalFallback.tsx75
1 files changed, 75 insertions, 0 deletions
diff --git a/packages/excalidraw/components/hoc/withInternalFallback.tsx b/packages/excalidraw/components/hoc/withInternalFallback.tsx
new file mode 100644
index 0000000..5906b30
--- /dev/null
+++ b/packages/excalidraw/components/hoc/withInternalFallback.tsx
@@ -0,0 +1,75 @@
+import React, { useLayoutEffect, useRef } from "react";
+import { useTunnels } from "../../context/tunnels";
+import { atom } from "../../editor-jotai";
+
+export const withInternalFallback = <P,>(
+ componentName: string,
+ Component: React.FC<P>,
+) => {
+ const renderAtom = atom(0);
+
+ const WrapperComponent: React.FC<
+ P & {
+ __fallback?: boolean;
+ }
+ > = (props) => {
+ const {
+ tunnelsJotai: { useAtom },
+ } = useTunnels();
+ // for rerenders
+ const [, setCounter] = useAtom(renderAtom);
+ // for initial & subsequent renders. Tracked as component state
+ // due to excalidraw multi-instance scanerios.
+ const metaRef = useRef({
+ // flag set on initial render to tell the fallback component to skip the
+ // render until mount counter are initialized. This is because the counter
+ // is initialized in an effect, and thus we could end rendering both
+ // components at the same time until counter is initialized.
+ preferHost: false,
+ counter: 0,
+ });
+
+ useLayoutEffect(() => {
+ const meta = metaRef.current;
+ setCounter((c) => {
+ const next = c + 1;
+ meta.counter = next;
+
+ return next;
+ });
+ return () => {
+ setCounter((c) => {
+ const next = c - 1;
+ meta.counter = next;
+ if (!next) {
+ meta.preferHost = false;
+ }
+ return next;
+ });
+ };
+ }, [setCounter]);
+
+ if (!props.__fallback) {
+ metaRef.current.preferHost = true;
+ }
+
+ // ensure we don't render fallback and host components at the same time
+ if (
+ // either before the counters are initialized
+ (!metaRef.current.counter &&
+ props.__fallback &&
+ metaRef.current.preferHost) ||
+ // or after the counters are initialized, and both are rendered
+ // (this is the default when host renders as well)
+ (metaRef.current.counter > 1 && props.__fallback)
+ ) {
+ return null;
+ }
+
+ return <Component {...props} />;
+ };
+
+ WrapperComponent.displayName = componentName;
+
+ return WrapperComponent;
+};