diff --git a/.changeset/bright-apes-sparkle.md b/.changeset/bright-apes-sparkle.md
new file mode 100644
index 0000000000..0afa858d4b
--- /dev/null
+++ b/.changeset/bright-apes-sparkle.md
@@ -0,0 +1,6 @@
+---
+"@khanacademy/wonder-blocks-core": major
+---
+
+- Remove `RenderState.Root` from exported enum
+- Change `useRenderState` to only return `RenderState.Initial` or `RenderState.Standard`
diff --git a/__docs__/wonder-blocks-core/exports.use-render-state.mdx b/__docs__/wonder-blocks-core/exports.use-render-state.mdx
index a532c93a99..d8f6ed0d85 100644
--- a/__docs__/wonder-blocks-core/exports.use-render-state.mdx
+++ b/__docs__/wonder-blocks-core/exports.use-render-state.mdx
@@ -1,8 +1,6 @@
import {Meta} from "@storybook/blocks";
-
+
# useRenderState()
@@ -16,6 +14,3 @@ The `useRenderState` hook will return either:
the initial rehydration render on the client.
- `RenderState.Standard` if the component renders on the client after the initial
rehydration.
-
-NOTE: Although the `RenderState` enum has a third state `Root`, this value is never
-returned by `useRenderState`.
diff --git a/packages/wonder-blocks-core/src/components/initial-fallback.tsx b/packages/wonder-blocks-core/src/components/initial-fallback.tsx
index f9b3b1be7b..6a8db42eb8 100644
--- a/packages/wonder-blocks-core/src/components/initial-fallback.tsx
+++ b/packages/wonder-blocks-core/src/components/initial-fallback.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
-import {RenderState, RenderStateContext} from "./render-state-context";
+import {RenderStateInternal, RenderStateContext} from "./render-state-context";
/**
* We use render functions so that we don't do any work unless we need to.
@@ -88,7 +88,9 @@ export default class InitialFallback extends React.Component {
// do their thing. Components don't mount during SSR, so we won't
// hit this when server-side rendering.
return (
-
+
{children()}
);
@@ -100,7 +102,9 @@ export default class InitialFallback extends React.Component {
// and they're not in charge of initiating the next render.
if (fallback) {
return (
-
+
{fallback()}
);
@@ -110,22 +114,20 @@ export default class InitialFallback extends React.Component {
return null;
}
- _maybeRender(
- renderState: typeof RenderState[keyof typeof RenderState],
- ): React.ReactNode {
+ _maybeRender(renderState: RenderStateInternal): React.ReactNode {
const {children, fallback} = this.props;
switch (renderState) {
- case RenderState.Root:
+ case RenderStateInternal.Root:
return this._renderAsRootComponent();
- case RenderState.Initial:
+ case RenderStateInternal.Initial:
// We're not the root component, so we just have to either
// render our placeholder or nothing.
// The second render is going to be triggered for us.
return fallback ? fallback() : null;
- case RenderState.Standard:
+ case RenderStateInternal.Standard:
// We have covered the SSR render, we're now rendering with
// standard rendering semantics.
return children();
@@ -156,7 +158,7 @@ export default class InitialFallback extends React.Component {
// We "fallthrough" to the root case. This is more obvious
// and maintainable code than just ignoring the no-fallthrough
// lint rule.
- return this._maybeRender(RenderState.Root);
+ return this._maybeRender(RenderStateInternal.Root);
}
}
diff --git a/packages/wonder-blocks-core/src/components/render-state-context.ts b/packages/wonder-blocks-core/src/components/render-state-context.ts
index 6b4c7d3e69..f7b8cae3d4 100644
--- a/packages/wonder-blocks-core/src/components/render-state-context.ts
+++ b/packages/wonder-blocks-core/src/components/render-state-context.ts
@@ -1,8 +1,57 @@
import * as React from "react";
+/**
+ * The possible states of rendering.
+ *
+ * This is used to determine if we are rendering our initial, hydrateable state
+ * or not. Initial renders must be consistent between the server and client so
+ * that hydration will succeed.
+ *
+ * We use a render state like this instead of a simple check for being mounted
+ * or not, or some other way of each component knowing if it is rendering itself
+ * for the first time so that we can avoid cascading initial renders where each
+ * component has to render itself and its children multiple times to reach a
+ * stable state. Instead, we track the initial render from the root of the tree
+ * and switch everything accordingly so that there are fewer additional renders.
+ */
export enum RenderState {
+ /**
+ * The initial render, either on the server or client.
+ */
+ Initial = "initial",
+
+ /**
+ * Any render after the initial render. Only occurs on the client.
+ */
+ Standard = "standard",
+}
+
+/**
+ * The internal states of rendering.
+ *
+ * This is different to the `RenderState` enum as this is internal to the
+ * Core package and solely for components that are going to provide new values
+ * to the render state context.
+ */
+export enum RenderStateInternal {
+ /**
+ * This is the root state. It indicates that nothing has actually changed
+ * then context value that tracks this. This is used solely by components
+ * that control the rendering state to know that they are in charge of
+ * that process.
+ */
Root = "root",
+
+ /**
+ * This indicates something has taken charge of the rendering state and
+ * components should render their initial render state that is hydrateable.
+ */
Initial = "initial",
+
+ /**
+ * This indicates that things are now rendering after the initial render
+ * and components can render without worrying about hydration.
+ */
Standard = "standard",
}
@@ -21,9 +70,9 @@ export enum RenderState {
* standard:
* means that we're all now doing non-SSR rendering
*/
-const RenderStateContext = React.createContext<
- typeof RenderState[keyof typeof RenderState]
->(RenderState.Root);
+const RenderStateContext = React.createContext(
+ RenderStateInternal.Root,
+);
RenderStateContext.displayName = "RenderStateContext";
export {RenderStateContext};
diff --git a/packages/wonder-blocks-core/src/components/render-state-root.tsx b/packages/wonder-blocks-core/src/components/render-state-root.tsx
index 5376a53711..5fcb33e7b7 100644
--- a/packages/wonder-blocks-core/src/components/render-state-root.tsx
+++ b/packages/wonder-blocks-core/src/components/render-state-root.tsx
@@ -1,9 +1,8 @@
import * as React from "react";
-import {RenderState, RenderStateContext} from "./render-state-context";
-import {useRenderState} from "../hooks/use-render-state";
+import {RenderStateInternal, RenderStateContext} from "./render-state-context";
-const {useEffect, useState} = React;
+const {useEffect, useState, useContext} = React;
type Props = {
children: React.ReactNode;
@@ -18,12 +17,12 @@ const RenderStateRoot = ({
throwIfNested = true,
}: Props): React.ReactElement => {
const [firstRender, setFirstRender] = useState(true);
- const renderState = useRenderState();
+ const renderState = useContext(RenderStateContext);
useEffect(() => {
setFirstRender(false);
}, []); // This effect will only run once.
- if (renderState !== RenderState.Root) {
+ if (renderState !== RenderStateInternal.Root) {
if (throwIfNested) {
throw new Error(
"There's already a above this instance in " +
@@ -35,7 +34,9 @@ const RenderStateRoot = ({
return <>{children}>;
}
- const value = firstRender ? RenderState.Initial : RenderState.Standard;
+ const value = firstRender
+ ? RenderStateInternal.Initial
+ : RenderStateInternal.Standard;
return (
diff --git a/packages/wonder-blocks-core/src/hooks/__tests__/use-unique-id.test.tsx b/packages/wonder-blocks-core/src/hooks/__tests__/use-unique-id.test.tsx
index b83d9ba157..0698317df2 100644
--- a/packages/wonder-blocks-core/src/hooks/__tests__/use-unique-id.test.tsx
+++ b/packages/wonder-blocks-core/src/hooks/__tests__/use-unique-id.test.tsx
@@ -43,7 +43,7 @@ describe("useUniqueIdWithoutMock", () => {
expect(factoryValues[0]).toBe(null);
});
- test("second client render retursn a unique id factory", () => {
+ test("second client render returns a unique id factory", () => {
// Arrange
const factoryValues: Array = [];
const TestComponent = (): React.ReactElement | null => {
@@ -89,20 +89,7 @@ describe("useUniqueIdWithoutMock", () => {
expect(factoryValues[1]).toBe(factoryValues[2]);
});
- it("should throw an error if it isn't a descendant of ", () => {
- // Arrange
-
- // Act
- const underTest = () =>
- renderHookStatic(() => useUniqueIdWithoutMock());
-
- // Assert
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
- `"Components using useUniqueIdWithoutMock() should be descendants of "`,
- );
- });
-
- it("Should minimize the number of renders it does", () => {
+ it("should minimize the number of renders it does", () => {
// Arrange
const values1: Array = [];
const TestComponent1 = (): React.ReactElement | null => {
@@ -213,19 +200,6 @@ describe("useUniqueIdWithMock", () => {
expect(factoryValues[1]).toBe(factoryValues[2]);
});
- it("should throw an error if it isn't a descendant of ", () => {
- // Arrange
-
- // Act
- const underTest = () =>
- renderHookStatic(() => useUniqueIdWithoutMock());
-
- // Assert
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
- `"Components using useUniqueIdWithoutMock() should be descendants of "`,
- );
- });
-
it("Should minimize the number of renders it does", () => {
// Arrange
const values1: Array = [];
diff --git a/packages/wonder-blocks-core/src/hooks/use-render-state.ts b/packages/wonder-blocks-core/src/hooks/use-render-state.ts
index b70bd0e22d..f0eb8fbcb1 100644
--- a/packages/wonder-blocks-core/src/hooks/use-render-state.ts
+++ b/packages/wonder-blocks-core/src/hooks/use-render-state.ts
@@ -2,9 +2,18 @@ import {useContext} from "react";
import {
RenderState,
+ RenderStateInternal,
RenderStateContext,
} from "../components/render-state-context";
-export const useRenderState =
- (): typeof RenderState[keyof typeof RenderState] =>
- useContext(RenderStateContext);
+export const useRenderState = (): RenderState => {
+ const rawRenderState = useContext(RenderStateContext);
+ // For consumers, they do not care if the render state is initial or
+ // root. That is solely info for the RenderStateRoot component.
+ // To everything else, it's just the initial render or standard render.
+ if (rawRenderState === RenderStateInternal.Standard) {
+ return RenderState.Standard;
+ } else {
+ return RenderState.Initial;
+ }
+};
diff --git a/packages/wonder-blocks-core/src/hooks/use-unique-id.ts b/packages/wonder-blocks-core/src/hooks/use-unique-id.ts
index 193eddd1c7..ac07b8e3bb 100644
--- a/packages/wonder-blocks-core/src/hooks/use-unique-id.ts
+++ b/packages/wonder-blocks-core/src/hooks/use-unique-id.ts
@@ -20,12 +20,6 @@ export const useUniqueIdWithMock = (scope?: string): IIdentifierFactory => {
const renderState = useRenderState();
const idFactory = useRef(null);
- if (renderState === RenderState.Root) {
- throw new Error(
- "Components using useUniqueIdWithMock() should be descendants of ",
- );
- }
-
if (renderState === RenderState.Initial) {
return SsrIDFactory;
}
@@ -50,12 +44,6 @@ export const useUniqueIdWithoutMock = (
const renderState = useRenderState();
const idFactory = useRef(null);
- if (renderState === RenderState.Root) {
- throw new Error(
- "Components using useUniqueIdWithoutMock() should be descendants of ",
- );
- }
-
if (renderState === RenderState.Initial) {
return null;
}