From 07f7f4073621f36bfe8fad21188325919078320d Mon Sep 17 00:00:00 2001 From: Jeff Yates Date: Tue, 27 Aug 2024 12:18:49 -0500 Subject: [PATCH] [initialfallback] Rename WithSSRPlaceholder to InitialFallback (#2304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: This renames `WithSSRPlaceholder` to more accurately reflect its purpose. This includes changing the `placeholder` prop to `fallback` so that we more closely match the `React.Suspense` API. ### To update code using `WithSSRPlaceholder`: 1. Find/replace `WithSSRPlaceholder placeholder=` in files using that component to `InitialFallback fallback=` 2. Find/replace `WithSSRPlaceholder` to `InitialFallback` Issue: XXX-XXXX ## Test plan: `yarn test` `yarn typecheck` `yarn start:storybook` and view the docs for the `InitialFallback` component Author: somewhatabstract Reviewers: jeresig Required Reviewers: Approved By: jeresig Checks: ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ codecov/project, ✅ Test (ubuntu-latest, 20.x, 2/2), ✅ Test (ubuntu-latest, 20.x, 1/2), ✅ Lint (ubuntu-latest, 20.x), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ⏭️ Chromatic - Skip on Release PR (changesets), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ⏭️ dependabot, ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ gerald, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x) Pull Request URL: https://github.com/Khan/wonder-blocks/pull/2304 --- .changeset/wicked-carrots-hope.md | 6 ++ ...ories.tsx => initial-fallback.stories.tsx} | 64 +++++++++--------- ...der.test.tsx => initial-fallback.test.tsx} | 67 +++++++++---------- .../__tests__/unique-id-provider.test.tsx | 8 +-- ...r-placeholder.tsx => initial-fallback.tsx} | 52 +++++++------- .../src/components/unique-id-provider.tsx | 10 +-- packages/wonder-blocks-core/src/index.ts | 2 +- .../src/components/media-layout.tsx | 6 +- 8 files changed, 108 insertions(+), 107 deletions(-) create mode 100644 .changeset/wicked-carrots-hope.md rename __docs__/wonder-blocks-core/{with-ssr-placeholder.stories.tsx => initial-fallback.stories.tsx} (79%) rename packages/wonder-blocks-core/src/components/__tests__/{with-ssr-placeholder.test.tsx => initial-fallback.test.tsx} (79%) rename packages/wonder-blocks-core/src/components/{with-ssr-placeholder.tsx => initial-fallback.tsx} (75%) diff --git a/.changeset/wicked-carrots-hope.md b/.changeset/wicked-carrots-hope.md new file mode 100644 index 000000000..36a4be7b2 --- /dev/null +++ b/.changeset/wicked-carrots-hope.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/wonder-blocks-core": major +"@khanacademy/wonder-blocks-layout": minor +--- + +Renamed `WithSSRPlaceholder` to `InitialFallback`. This includes changing the `placeholder` prop to `fallback` so it's closer to `Suspense` usage. diff --git a/__docs__/wonder-blocks-core/with-ssr-placeholder.stories.tsx b/__docs__/wonder-blocks-core/initial-fallback.stories.tsx similarity index 79% rename from __docs__/wonder-blocks-core/with-ssr-placeholder.stories.tsx rename to __docs__/wonder-blocks-core/initial-fallback.stories.tsx index 0b00044e4..f4c6a118e 100644 --- a/__docs__/wonder-blocks-core/with-ssr-placeholder.stories.tsx +++ b/__docs__/wonder-blocks-core/initial-fallback.stories.tsx @@ -2,16 +2,16 @@ import * as React from "react"; import type {Meta, StoryObj} from "@storybook/react"; import {Body} from "@khanacademy/wonder-blocks-typography"; -import {View, WithSSRPlaceholder} from "@khanacademy/wonder-blocks-core"; +import {View, InitialFallback} from "@khanacademy/wonder-blocks-core"; import packageConfig from "../../packages/wonder-blocks-core/package.json"; import ComponentInfo from "../../.storybook/components/component-info"; -type StoryComponentType = StoryObj; +type StoryComponentType = StoryObj; export default { - title: "Packages / Core / WithSSRPlaceholder", - component: WithSSRPlaceholder, + title: "Packages / Core / InitialFallback", + component: InitialFallback, parameters: { componentSubtitle: ( ( + fallback: (): React.ReactElement => ( This gets rendered on server, and also on the client for the very first render (the rehydration render) @@ -46,19 +46,19 @@ export default { ), }, -} as Meta; +} as Meta; export const Default: StoryComponentType = {}; export const WithoutPlaceholder: StoryComponentType = () => ( - + {() => ( This is rendered only by the client, while nothing was rendered on the server. )} - + ); WithoutPlaceholder.parameters = { @@ -104,24 +104,24 @@ export const NestedComponent: StoryComponentType = (): React.ReactElement => { The list below should have three render entries; root placeholder, root children render, and child children render. If there are two child renders that means that the second forced - render is still occurring for nested WithSSRPlaceholder - components, which would be a bug. + render is still occurring for nested InitialFallback components, + which would be a bug.
    - And below this is the actual WithSSRPlaceholder nesting, which + And below this is the actual InitialFallback nesting, which should just show the child render. - ( + ( {trackAndRender("Root: placeholder")} )} > {() => { addTrackedRender("Root: render"); return ( - ( + ( {trackAndRender( "Child: placeholder (should never see me)", @@ -132,10 +132,10 @@ export const NestedComponent: StoryComponentType = (): React.ReactElement => { {() => ( {trackAndRender("Child: render")} )} - + ); }} - + ); }; @@ -143,7 +143,7 @@ export const NestedComponent: StoryComponentType = (): React.ReactElement => { NestedComponent.parameters = { docs: { description: { - story: "Here, we nest two `WithSSRPlaceholder` components and use an array to track rendering, so that we can see how only the top level `WithSSRPlaceholder` component skips the initial render.", + story: "Here, we nest two `InitialFallback` components and use an array to track rendering, so that we can see how only the top level `InitialFallback` component skips the initial render.", }, }, }; @@ -186,19 +186,19 @@ export const SideBySide: StoryComponentType = (): React.ReactElement => {
      - And below this are the WithSSRPlaceholder component trees, which + And below this are the InitialFallback component trees, which should just show their child renders. - ( + ( {trackAndRender("Root 1: placeholder")} )} > {() => { addTrackedRender("Root 1: render"); return ( - ( + ( {trackAndRender( "Child 1: placeholder (should never see me)", @@ -209,20 +209,20 @@ export const SideBySide: StoryComponentType = (): React.ReactElement => { {() => ( {trackAndRender("Child 1: render")} )} - + ); }} - - ( + + ( {trackAndRender("Root 2: placeholder")} )} > {() => { addTrackedRender("Root 2: render"); return ( - ( + ( {trackAndRender( "Child 2: placeholder (should never see me)", @@ -233,10 +233,10 @@ export const SideBySide: StoryComponentType = (): React.ReactElement => { {() => ( {trackAndRender("Child 2: render")} )} - + ); }} - + ); }; @@ -244,7 +244,7 @@ export const SideBySide: StoryComponentType = (): React.ReactElement => { SideBySide.parameters = { docs: { description: { - story: "In this example, we have side-by-side `WithSSRPlaceholder` components. This demonstrates how component non-nested `WithSSRPlaceholder` components independently track the first render.", + story: "In this example, we have side-by-side `InitialFallback` components. This demonstrates how component non-nested `InitialFallback` components independently track the first render.", }, }, }; diff --git a/packages/wonder-blocks-core/src/components/__tests__/with-ssr-placeholder.test.tsx b/packages/wonder-blocks-core/src/components/__tests__/initial-fallback.test.tsx similarity index 79% rename from packages/wonder-blocks-core/src/components/__tests__/with-ssr-placeholder.test.tsx rename to packages/wonder-blocks-core/src/components/__tests__/initial-fallback.test.tsx index 3f3c1f8c0..0d111e9d0 100644 --- a/packages/wonder-blocks-core/src/components/__tests__/with-ssr-placeholder.test.tsx +++ b/packages/wonder-blocks-core/src/components/__tests__/initial-fallback.test.tsx @@ -2,22 +2,22 @@ import * as React from "react"; import * as ReactDOMServer from "react-dom/server"; import {render} from "@testing-library/react"; -import WithSSRPlaceholder from "../with-ssr-placeholder"; +import InitialFallback from "../initial-fallback"; import {RenderStateRoot} from "../render-state-root"; -describe("WithSSRPlaceholder", () => { +describe("InitialFallback", () => { describe("client-side rendering", () => { test("calls placeholder render first, then the actual content render", async () => { // Arrange const mockPlaceholder = jest.fn(() => null); await new Promise((resolve: any) => { const nodes = ( - + {() => { resolve(); return null; }} - + ); // Act @@ -43,15 +43,15 @@ describe("WithSSRPlaceholder", () => { }; const placeholder = () => ( - + {mockChildrenNotCalled} - + ); const nodes = ( - + {() => null} - + ); // Act @@ -60,7 +60,7 @@ describe("WithSSRPlaceholder", () => { // Assert // Our promise doesn't resolve until the placeholder of our nested - // WithSSRPlaceholder is rendered, therefore if we get here it means + // InitialFallback is rendered, therefore if we get here it means // that the test is doing what we'd expect. // In addition, if our code is working right, the children of the // nested placeholder should have been skipped. @@ -73,18 +73,18 @@ describe("WithSSRPlaceholder", () => { const mockPlaceholderNotCalled = jest.fn(() => null); await new Promise((resolve: any) => { const nodes = ( - + {() => ( - {() => { resolve(); return null; }} - + )} - + ); // Act @@ -92,7 +92,7 @@ describe("WithSSRPlaceholder", () => { }); // Assert - // Our promise doesn't resolve until the children of our nested WithSSRPlaceholder + // Our promise doesn't resolve until the children of our nested InitialFallback // are rendered, therefore we don't get here until that and so if the // parent placeholder has been called, it must have been called first. // In addition, if our code is working right, the placeholder of the @@ -110,9 +110,9 @@ describe("WithSSRPlaceholder", () => { const mockPlaceholder = jest.fn(() => null); const nodes = ( - + {mockChildren} - + ); // Act @@ -128,9 +128,9 @@ describe("WithSSRPlaceholder", () => { const mockChildren = jest.fn(() => null); const nodes = ( - + {mockChildren} - + ); // Act @@ -146,15 +146,15 @@ describe("WithSSRPlaceholder", () => { // Arrange const expectation = "CHILD PLACEHOLDER"; const placeholder = ( - expectation}> + expectation}> {() => "This won't render"} - + ); const nodes = ( - placeholder}> + placeholder}> {() => "This won't render"} - + ); // Act @@ -167,16 +167,15 @@ describe("WithSSRPlaceholder", () => { test("in parent children, renders as null", () => { // Arrange const placeholder = ( - // @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call. - + {() => "This won't render"} - + ); const nodes = ( - placeholder}> + placeholder}> {() => "This won't render"} - + ); // Act @@ -195,12 +194,12 @@ describe("WithSSRPlaceholder", () => { await new Promise((resolve: any) => { const nodes = ( - + {() => { resolve(); return null; }} - + ); @@ -222,9 +221,9 @@ describe("WithSSRPlaceholder", () => { const nodes = ( - + {mockChildren} - + ); @@ -242,9 +241,9 @@ describe("WithSSRPlaceholder", () => { const nodes = ( - + {mockChildren} - + ); diff --git a/packages/wonder-blocks-core/src/components/__tests__/unique-id-provider.test.tsx b/packages/wonder-blocks-core/src/components/__tests__/unique-id-provider.test.tsx index 24e522731..a44adce41 100644 --- a/packages/wonder-blocks-core/src/components/__tests__/unique-id-provider.test.tsx +++ b/packages/wonder-blocks-core/src/components/__tests__/unique-id-provider.test.tsx @@ -7,7 +7,7 @@ import View from "../view"; import SsrIDFactory from "../../util/ssr-id-factory"; import UniqueIDFactory from "../../util/unique-id-factory"; import UniqueIDProvider from "../unique-id-provider"; -import WithSSRPlaceholder from "../with-ssr-placeholder"; +import InitialFallback from "../initial-fallback"; import {RenderStateRoot} from "../render-state-root"; describe("UniqueIDProvider", () => { @@ -138,19 +138,19 @@ describe("UniqueIDProvider", () => { }); }); - describe("inside a WithSSRPlaceholder", () => { + describe("inside a InitialFallback", () => { test("it should pass an id to its children", () => { // Arrange const foo = jest.fn(() => null); const nodes = ( - + {() => ( {/* @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call. | TS2554 - Expected 0 arguments, but got 1. */} {(ids: any) => foo(ids.get(""))} )} - + ); // Act diff --git a/packages/wonder-blocks-core/src/components/with-ssr-placeholder.tsx b/packages/wonder-blocks-core/src/components/initial-fallback.tsx similarity index 75% rename from packages/wonder-blocks-core/src/components/with-ssr-placeholder.tsx rename to packages/wonder-blocks-core/src/components/initial-fallback.tsx index e0b851ec7..f9b3b1be7 100644 --- a/packages/wonder-blocks-core/src/components/with-ssr-placeholder.tsx +++ b/packages/wonder-blocks-core/src/components/initial-fallback.tsx @@ -14,15 +14,14 @@ type Props = { */ children: () => React.ReactNode; /** - * What to render during server-side rendering, or null not to - * render anything. + * What to initially render, or null if nothing should be rendered. * - * NOTE: Make sure the placeholder will render the same for both + * NOTE: Make sure the fallback will render the same for both * client and server -- that is, it does the same thing for both * the server-side renderer and the rehydration -- or it defeats - * the purpose of using the WithSSRPlaceholder component. + * the purpose of using the InitialFallback component. */ - placeholder: (() => React.ReactNode) | null; + fallback: (() => React.ReactNode) | null; }; type State = { @@ -38,25 +37,25 @@ type State = { * is rendered on the server. Therefore, this component also disables rendering * the first time around on the client. * - * If `WithSSRPlaceholder` components are nested within one another, the root - * `WithSSRPlaceholder` component will handle the initial render, but nested - * `WithSSRPlaceholder` components will delegate to the root one, meaning that + * If `InitialFallback` components are nested within one another, the root + * `InitialFallback` component will handle the initial render, but nested + * `InitialFallback` components will delegate to the root one, meaning that * we don't cascade delayed rendering down the component tree. This will also be * the case across portal boundaries. * * ## Usage * * ```js - * import {WithSSRPlaceholder} from "@khanacademy/wonder-blocks-core"; + * import {InitialFallback} from "@khanacademy/wonder-blocks-core"; * - *
      Renders on the server!
      }> + *
      Renders on the server!
      }> * {() => ( *
      This is rendered only by the client, for all renders after the rehydration render
      * )} - *
      + * * ``` */ -export default class WithSSRPlaceholder extends React.Component { +export default class InitialFallback extends React.Component { state: State = { mounted: false, }; @@ -77,7 +76,7 @@ export default class WithSSRPlaceholder extends React.Component { _renderAsRootComponent(): React.ReactNode { const {mounted} = this.state; - const {children, placeholder} = this.props; + const {children, fallback} = this.props; // We are the first component in the tree. // We are in control of instigating a second render for our @@ -85,8 +84,9 @@ export default class WithSSRPlaceholder extends React.Component { this._isTheRootComponent = true; if (mounted) { - // This is our second non-SSR render, so let's tell everyone to - // do their thing. + // This is our second render, so let's tell everyone to + // do their thing. Components don't mount during SSR, so we won't + // hit this when server-side rendering. return ( {children()} @@ -94,14 +94,14 @@ export default class WithSSRPlaceholder extends React.Component { ); } - // OK, this is the very first render. - // If we have a placeholder, we render it, and ensure that any - // nested SSR components know we're still on that first render - // but they're not in charge of instigating the second render. - if (placeholder) { + // OK, this is the very first initial render. + // If we have a fallback, we render it, and ensure that any + // nested components know we're still on that initial render + // and they're not in charge of initiating the next render. + if (fallback) { return ( - {placeholder()} + {fallback()} ); } @@ -113,7 +113,7 @@ export default class WithSSRPlaceholder extends React.Component { _maybeRender( renderState: typeof RenderState[keyof typeof RenderState], ): React.ReactNode { - const {children, placeholder} = this.props; + const {children, fallback} = this.props; switch (renderState) { case RenderState.Root: @@ -123,11 +123,7 @@ export default class WithSSRPlaceholder extends React.Component { // 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. - if (placeholder) { - return placeholder(); - } - // Otherwise, we render nothing. - return null; + return fallback ? fallback() : null; case RenderState.Standard: // We have covered the SSR render, we're now rendering with @@ -137,7 +133,7 @@ export default class WithSSRPlaceholder extends React.Component { // There are edge cases where for some reason, we get an unknown // context value here. So far it seems to be when we're nested in a - // v1 WithSSRPlaceholder equivalent component, or in some older + // v1 InitialFallback equivalent component, or in some older // React v16 situations where we're nested in the provider of a // different context. // diff --git a/packages/wonder-blocks-core/src/components/unique-id-provider.tsx b/packages/wonder-blocks-core/src/components/unique-id-provider.tsx index 094224fb9..953cc494f 100644 --- a/packages/wonder-blocks-core/src/components/unique-id-provider.tsx +++ b/packages/wonder-blocks-core/src/components/unique-id-provider.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import WithSSRPlaceholder from "./with-ssr-placeholder"; +import InitialFallback from "./initial-fallback"; import UniqueIDFactory from "../util/unique-id-factory"; import SsrIDFactory from "../util/ssr-id-factory"; @@ -79,7 +79,7 @@ export default class UniqueIDProvider extends React.Component { // If this is our first render, we're going to stop right here. // Note: `firstRender` will be `false` on the first render if this - // component is a descendant of a `WithSSRPlaceholder`. + // component is a descendant of a `InitialFallback`. if (firstRender) { if (mockOnFirstRender) { // We're allowing an initial render, so let's pass our mock @@ -99,13 +99,13 @@ export default class UniqueIDProvider extends React.Component { } render(): React.ReactNode { - // Here we use the WithSSRPlaceholder component to control + // Here we use the InitialFallback component to control // when we render and whether we provide a mock or real // identifier factory. return ( - this._performRender(true)}> + this._performRender(true)}> {() => this._performRender(false)} - + ); } } diff --git a/packages/wonder-blocks-core/src/index.ts b/packages/wonder-blocks-core/src/index.ts index 8a1c6608e..0d9dd989c 100644 --- a/packages/wonder-blocks-core/src/index.ts +++ b/packages/wonder-blocks-core/src/index.ts @@ -7,7 +7,7 @@ import type { export {default as Text} from "./components/text"; export {default as View} from "./components/view"; -export {default as WithSSRPlaceholder} from "./components/with-ssr-placeholder"; +export {default as InitialFallback} from "./components/initial-fallback"; export {default as IDProvider} from "./components/id-provider"; export {default as UniqueIDProvider} from "./components/unique-id-provider"; export {default as addStyle} from "./util/add-style"; diff --git a/packages/wonder-blocks-layout/src/components/media-layout.tsx b/packages/wonder-blocks-layout/src/components/media-layout.tsx index e22debe49..08a5f9566 100644 --- a/packages/wonder-blocks-layout/src/components/media-layout.tsx +++ b/packages/wonder-blocks-layout/src/components/media-layout.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import type {StyleDeclaration} from "aphrodite"; import type {StyleType} from "@khanacademy/wonder-blocks-core"; -import {WithSSRPlaceholder} from "@khanacademy/wonder-blocks-core"; +import {InitialFallback} from "@khanacademy/wonder-blocks-core"; import MediaLayoutContext from "./media-layout-context"; import type {MediaSize, MediaSpec} from "../util/types"; import type {Context} from "./media-layout-context"; @@ -245,9 +245,9 @@ class MediaLayoutInternal extends React.Component { render() { return ( - this.renderContent(true)}> + this.renderContent(true)}> {() => this.renderContent(this.isUnsupportedEnvironment())} - + ); } }