Dennis Solomon
Dennis Solomon

Reputation: 23

Client sided component inside Supense fallback never fully loads

I'm new to ReactJs.

I made a background-color wrapper, to wrap my child component's color as background-color. Yes it's a bizarre edge-case and I wasn't able to find an alternative, but regardless;

When using the Wrapper in a page, everything functions as expected: the children's color is set to the parents. However when using it inside a Suspense's fallback, the code which actually fetches and update the color, never gets called.

backgroundcolor.tsx

"use client";

import {
  Children,
  cloneElement,
  Fragment,
  isValidElement,
  ReactElement,
  ReactNode,
  useEffect,
  useState,
} from "react";

export function BackgroundColorWrapper({
  children,
}: Readonly<{
  children: ReactNode;
}>) {
  const [parentBgColor, setParentBgColor] = useState<string | null>(null);
  const [isVisible, setIsVisible] = useState(false);

  const childRef = (node: HTMLElement | null) => {
    console.log("BackgroundColorWrapper childRef");
    if (node) {
      let currentElement = node.parentElement;
      while (currentElement) {
        const computedStyle = window.getComputedStyle(currentElement);
        const backgroundColor = computedStyle.backgroundColor;
        if (
          backgroundColor !== "transparent" &&
          backgroundColor !== "rgba(0, 0, 0, 0)" &&
          backgroundColor !== "inherit"
        ) {
          setParentBgColor(backgroundColor);
          break;
        }
        currentElement = currentElement.parentElement;
      }
      if (!currentElement) {
        setParentBgColor("inherit");
      }
    }
  };

  useEffect(() => {
    console.log("BackgroundColorWrapper useEffect");
    if (parentBgColor !== null) {
      const timer = setTimeout(() => {
        setIsVisible(true);
      }, 50);
      return () => clearTimeout(timer);
    }
  }, [parentBgColor]);

  return (
    <Fragment>
      {Children.map(children, (child) => {
        if (!isValidElement(child)) {
          return child;
        }
        const test = cloneElement(child as ReactElement, {
          ref: childRef,
          style: {
            ...child.props.style,
            visibility: parentBgColor === null ? "hidden" : "visible",
            color: parentBgColor,
            opacity: isVisible ? 1 : 0,
            transition: "opacity 0.5s ease",
          },
        });
        console.log("child", child);
        return test;
      })}
    </Fragment>
  );
}

export default BackgroundColorWrapper;

Nor, console.log("BackgroundColorWrapper childRef"); or console.log("BackgroundColorWrapper useEffect"); ever get called when inside a fallback.

And just to clarify:

// This works fine
  return (
    <BackgroundColorWrapper>
      // xyz
    </BackgroundColorWrapper>
  );
// This does not
  return (
    <Suspense
      fallback={
        <BackgroundColorWrapper>
          // xyz
        </BackgroundColorWrapper>
      }
    ></Suspense>
  );

As requested MRE:

import BackgroundColorWrapper from "@/components/wrappers/backgroundcolor";
import { Suspense } from "react";

export default async function ExamplePage() {
  return (
    <main
      style={{
        display: "flex",
        minHeight: "100vh",
        alignItems: "center",
        justifyContent: "center",
        gap: "2rem",
        backgroundColor: "white",
        padding: "2rem",
      }}
    >
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          borderRadius: "0.5rem",
          border: "1px solid #e5e7eb",
          padding: "1.5rem",
          boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
          transition: "box-shadow 0.2s",
        }}
      >
        <h2
          style={{
            marginBottom: "1rem",
            fontSize: "1.125rem",
            fontWeight: "600",
            color: "#374151",
          }}
        >
          Direct
        </h2>
        <div
          style={{
            height: "4rem",
            width: "4rem",
          }}
        >
          <LoadingUI />
        </div>
      </div>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          borderRadius: "0.5rem",
          border: "1px solid #e5e7eb",
          padding: "1.5rem",
          boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
          transition: "box-shadow 0.2s",
        }}
      >
        <h2
          style={{
            marginBottom: "1rem",
            fontSize: "1.125rem",
            fontWeight: "600",
            color: "#374151",
          }}
        >
          Suspense
        </h2>
        <div
          style={{
            height: "4rem",
            width: "4rem",
          }}
        >
          <Suspense fallback={<LoadingUI />}>
            <ExampleProcessor />
          </Suspense>
        </div>
      </div>
    </main>
  );
}

function SimpleCircle() {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="50" fill="black" />
      <circle cx="50" cy="50" r="25" fill="currentColor" />
    </svg>
  );
}

function LoadingUI() {
  return (
    <div>
      <BackgroundColorWrapper>
        <SimpleCircle />
      </BackgroundColorWrapper>
    </div>
  );
}

async function ExampleProcessor() {
  // wait 10 seconds
  await new Promise((resolve) => setTimeout(resolve, 10000));

  // Return the example component
  return (
    <h4
      style={{
        marginBottom: "1rem",
        fontSize: "1.125rem",
        fontWeight: "600",
        color: "#374151",
      }}
    >
      DONE
    </h4>
  );
}

Upvotes: 1

Views: 27

Answers (0)

Related Questions