From 078d26f92d1f09449f76c4199704b1d60632f34c Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Thu, 28 Aug 2025 15:34:06 +0530 Subject: [PATCH 1/4] refactor: add server-side rendering functionality and related tests in ts --- packages/block-library/tsconfig.json | 1 + packages/editor/tsconfig.json | 1 + packages/server-side-render/README.md | 21 ++---- .../src/{hook.js => hook.ts} | 70 +++++++++++++------ .../src/{index.js => index.ts} | 7 +- ...-side-render.js => server-side-render.tsx} | 70 ++++++++++++++----- .../src/test/{index.js => index.ts} | 0 packages/server-side-render/tsconfig.json | 25 +++++++ tsconfig.json | 1 + 9 files changed, 138 insertions(+), 58 deletions(-) rename packages/server-side-render/src/{hook.js => hook.ts} (64%) rename packages/server-side-render/src/{index.js => index.ts} (76%) rename packages/server-side-render/src/{server-side-render.js => server-side-render.tsx} (71%) rename packages/server-side-render/src/test/{index.js => index.ts} (100%) create mode 100644 packages/server-side-render/tsconfig.json diff --git a/packages/block-library/tsconfig.json b/packages/block-library/tsconfig.json index a8423ee4a27093..4710ae65c409e7 100644 --- a/packages/block-library/tsconfig.json +++ b/packages/block-library/tsconfig.json @@ -31,6 +31,7 @@ { "path": "../keycodes" }, { "path": "../primitives" }, { "path": "../rich-text" }, + { "path": "../server-side-render" }, { "path": "../url" }, { "path": "../wordcount" } ], diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 00a8f3860e2925..c9136a0b2e7e1a 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -29,6 +29,7 @@ { "path": "../plugins" }, { "path": "../private-apis" }, { "path": "../rich-text" }, + { "path": "../server-side-render" }, { "path": "../url" }, { "path": "../warning" }, { "path": "../wordcount" } diff --git a/packages/server-side-render/README.md b/packages/server-side-render/README.md index fa5065fe263926..78658c64184eb7 100644 --- a/packages/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -63,20 +63,12 @@ function Example() { _Parameters_ -- _props_ `Object`: Component props. -- _props.block_ `string`: The identifier of the block to be serverside rendered. -- _props.attributes_ `Object`: The block attributes to be sent to the server for rendering. -- _props.className_ `[string]`: Additional classes to apply to the wrapper element. -- _props.httpMethod_ `[string]`: The HTTP method to use ('GET' or 'POST'). Default is 'GET' -- _props.urlQueryArgs_ `[Object]`: Additional query arguments to append to the request URL. -- _props.skipBlockSupportAttributes_ `[boolean]`: Whether to remove block support attributes before sending. -- _props.EmptyResponsePlaceholder_ `[Function]`: Component rendered when the API response is empty. -- _props.ErrorResponsePlaceholder_ `[Function]`: Component rendered when the API response is an error. -- _props.LoadingResponsePlaceholder_ `[Function]`: Component rendered while the API request is loading. +- _props_ `ServerSideRenderWithPostIdProps`: Component props. +- _props.urlQueryArgs_ `ServerSideRenderWithPostIdProps[ 'urlQueryArgs' ]`: Additional query arguments to append to the request URL. _Returns_ -- `JSX.Element`: The rendered server-side content. +- The rendered server-side content. ### useServerSideRender @@ -112,12 +104,7 @@ function MyServerSideRender( { attributes, block } ) { _Parameters_ -- _args_ `Object`: The hook configuration object. -- _args.attributes_ `Object`: The block attributes to be sent to the server for rendering. -- _args.block_ `string`: The identifier of the block to be serverside rendered. Example: 'core/archives'. -- _args.skipBlockSupportAttributes_ `[boolean]`: Whether to remove block support attributes before sending. -- _args.httpMethod_ `[string]`: The HTTP method to use ('GET' or 'POST'). Default is 'GET'. -- _args.urlQueryArgs_ `[Object]`: Additional query arguments to append to the request URL. +- _args_ `UseServerSideRenderArgs`: The hook configuration object. _Returns_ diff --git a/packages/server-side-render/src/hook.js b/packages/server-side-render/src/hook.ts similarity index 64% rename from packages/server-side-render/src/hook.js rename to packages/server-side-render/src/hook.ts index 1d19a6e7c170b1..991b8f3ec385bd 100644 --- a/packages/server-side-render/src/hook.js +++ b/packages/server-side-render/src/hook.ts @@ -5,9 +5,15 @@ import { debounce } from '@wordpress/compose'; import { useEffect, useState, useRef } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; + +// @ts-expect-error __experimentalSanitizeBlockAttributes is not typed import { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks'; -export function rendererPath( block, attributes = null, urlQueryArgs = {} ) { +export function rendererPath( + block: string, + attributes: Record< string, unknown > | null = null, + urlQueryArgs: Record< string, unknown > = {} +): string { return addQueryArgs( `/wp/v2/block-renderer/${ block }`, { context: 'edit', ...( null !== attributes ? { attributes } : {} ), @@ -15,7 +21,9 @@ export function rendererPath( block, attributes = null, urlQueryArgs = {} ) { } ); } -export function removeBlockSupportAttributes( attributes ) { +export function removeBlockSupportAttributes( + attributes: Record< string, unknown > +): Record< string, unknown > { const { backgroundColor, borderColor, @@ -35,7 +43,7 @@ export function removeBlockSupportAttributes( attributes ) { spacing, typography, ...restStyles - } = attributes?.style || {}; + } = ( attributes?.style as Record< string, unknown > ) || {}; return { ...restAttributes, @@ -44,11 +52,32 @@ export function removeBlockSupportAttributes( attributes ) { } /** - * @typedef {Object} ServerSideRenderResponse - * @property {string} status - The current request status: 'idle', 'loading', 'success', or 'error'. - * @property {string} [content] - The rendered block content (available when status is 'success'). - * @property {string} [error] - The error message (available when status is 'error'). + * Server-side render response object. */ +export interface ServerSideRenderResponse { + /** The current request status: 'idle', 'loading', 'success', or 'error'. */ + status: 'idle' | 'loading' | 'success' | 'error'; + /** The rendered block content (available when status is 'success'). */ + content?: string; + /** The error message (available when status is 'error'). */ + error?: string; +} + +/** + * Configuration object for the useServerSideRender hook. + */ +export interface UseServerSideRenderArgs { + /** The block attributes to be sent to the server for rendering. */ + attributes: Record< string, unknown >; + /** The identifier of the block to be serverside rendered. Example: 'core/archives'. */ + block: string; + /** Whether to remove block support attributes before sending. */ + skipBlockSupportAttributes?: boolean; + /** The HTTP method to use ('GET' or 'POST'). Default is 'GET'. */ + httpMethod?: 'GET' | 'POST'; + /** Additional query arguments to append to the request URL. */ + urlQueryArgs?: Record< string, unknown >; +} /** * A hook for server-side rendering a preview of dynamic blocks to display in the editor. @@ -82,18 +111,17 @@ export function removeBlockSupportAttributes( attributes ) { * } * ``` * - * @param {Object} args The hook configuration object. - * @param {Object} args.attributes The block attributes to be sent to the server for rendering. - * @param {string} args.block The identifier of the block to be serverside rendered. Example: 'core/archives'. - * @param {boolean} [args.skipBlockSupportAttributes=false] Whether to remove block support attributes before sending. - * @param {string} [args.httpMethod='GET'] The HTTP method to use ('GET' or 'POST'). Default is 'GET'. - * @param {Object} [args.urlQueryArgs] Additional query arguments to append to the request URL. + * @param args The hook configuration object. * - * @return {ServerSideRenderResponse} The server-side render response object. + * @return The server-side render response object. */ -export function useServerSideRender( args ) { - const [ response, setResponse ] = useState( { status: 'idle' } ); - const shouldDebounceRef = useRef( false ); +export function useServerSideRender( + args: UseServerSideRenderArgs +): ServerSideRenderResponse { + const [ response, setResponse ] = useState< ServerSideRenderResponse >( { + status: 'idle', + } ); + const shouldDebounceRef = useRef< boolean >( false ); const { attributes, @@ -103,11 +131,11 @@ export function useServerSideRender( args ) { urlQueryArgs, } = args; - let sanitizedAttributes = + let sanitizedAttributes: Record< string, unknown > | null = attributes && __experimentalSanitizeBlockAttributes( block, attributes ); - if ( skipBlockSupportAttributes ) { + if ( skipBlockSupportAttributes && sanitizedAttributes ) { sanitizedAttributes = removeBlockSupportAttributes( sanitizedAttributes ); } @@ -128,7 +156,7 @@ export function useServerSideRender( args ) { { setResponse( { status: 'loading' } ); - apiFetch( { + apiFetch< { rendered: string } >( { path, method: isPostRequest ? 'POST' : 'GET', body, @@ -145,7 +173,7 @@ export function useServerSideRender( args ) { content: res ? res.rendered : '', } ); } ) - .catch( ( error ) => { + .catch( ( error: Error ) => { // The request was aborted, do not update the response. if ( error.name === 'AbortError' ) { return; diff --git a/packages/server-side-render/src/index.js b/packages/server-side-render/src/index.ts similarity index 76% rename from packages/server-side-render/src/index.js rename to packages/server-side-render/src/index.ts index 28bc8abfec43bd..245d52ceba27b3 100644 --- a/packages/server-side-render/src/index.js +++ b/packages/server-side-render/src/index.ts @@ -14,7 +14,12 @@ import { useServerSideRender } from './hook'; * import ServerSideRender from '@wordpress/server-side-render'; * ``` */ -const ServerSideRenderCompat = ServerSideRenderWithPostId; +const ServerSideRenderCompat = + ServerSideRenderWithPostId as typeof ServerSideRenderWithPostId & { + ServerSideRender: typeof ServerSideRenderWithPostId; + useServerSideRender: typeof useServerSideRender; + }; + ServerSideRenderCompat.ServerSideRender = ServerSideRenderWithPostId; ServerSideRenderCompat.useServerSideRender = useServerSideRender; diff --git a/packages/server-side-render/src/server-side-render.js b/packages/server-side-render/src/server-side-render.tsx similarity index 71% rename from packages/server-side-render/src/server-side-render.js rename to packages/server-side-render/src/server-side-render.tsx index 811b0f1c1b0b10..02f23a6d83e568 100644 --- a/packages/server-side-render/src/server-side-render.js +++ b/packages/server-side-render/src/server-side-render.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentType, ReactNode } from 'react'; + /** * WordPress dependencies */ @@ -16,10 +21,23 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { useServerSideRender } from './hook'; +import type { UseServerSideRenderArgs } from './hook'; const EMPTY_OBJECT = {}; -function DefaultEmptyResponsePlaceholder( { className } ) { +interface PlaceholderProps { + className?: string; +} + +interface ErrorPlaceholderProps extends PlaceholderProps { + message?: string; +} + +interface LoadingPlaceholderProps { + children?: ReactNode; +} + +function DefaultEmptyResponsePlaceholder( { className }: PlaceholderProps ) { return ( { __( 'Block rendered as empty.' ) } @@ -27,16 +45,21 @@ function DefaultEmptyResponsePlaceholder( { className } ) { ); } -function DefaultErrorResponsePlaceholder( { message, className } ) { +function DefaultErrorResponsePlaceholder( { + message, + className, +}: ErrorPlaceholderProps ) { const errorMessage = sprintf( // translators: %s: error message describing the problem __( 'Error loading block: %s' ), - message + message || 'Unknown error' ); return { errorMessage }; } -function DefaultLoadingResponsePlaceholder( { children } ) { +function DefaultLoadingResponsePlaceholder( { + children, +}: LoadingPlaceholderProps ) { const [ showLoader, setShowLoader ] = useState( false ); useEffect( () => { @@ -69,7 +92,14 @@ function DefaultLoadingResponsePlaceholder( { children } ) { ); } -export function ServerSideRender( props ) { +interface ServerSideRenderProps extends UseServerSideRenderArgs { + className?: string; + EmptyResponsePlaceholder?: ComponentType< PlaceholderProps >; + ErrorResponsePlaceholder?: ComponentType< ErrorPlaceholderProps >; + LoadingResponsePlaceholder?: ComponentType< LoadingPlaceholderProps >; +} + +export function ServerSideRender( props: ServerSideRenderProps ) { const prevContentRef = useRef( '' ); const { className, @@ -108,7 +138,12 @@ export function ServerSideRender( props ) { return ; } - return { content }; + return { content || '' }; +} + +interface ServerSideRenderWithPostIdProps + extends Omit< ServerSideRenderProps, 'urlQueryArgs' > { + urlQueryArgs?: Record< string, unknown >; } /** @@ -135,28 +170,25 @@ export function ServerSideRender( props ) { * } * ``` * - * @param {Object} props Component props. - * @param {string} props.block The identifier of the block to be serverside rendered. - * @param {Object} props.attributes The block attributes to be sent to the server for rendering. - * @param {string} [props.className] Additional classes to apply to the wrapper element. - * @param {string} [props.httpMethod='GET'] The HTTP method to use ('GET' or 'POST'). Default is 'GET' - * @param {Object} [props.urlQueryArgs] Additional query arguments to append to the request URL. - * @param {boolean} [props.skipBlockSupportAttributes=false] Whether to remove block support attributes before sending. - * @param {Function} [props.EmptyResponsePlaceholder] Component rendered when the API response is empty. - * @param {Function} [props.ErrorResponsePlaceholder] Component rendered when the API response is an error. - * @param {Function} [props.LoadingResponsePlaceholder] Component rendered while the API request is loading. + * @param props Component props. + * @param props.urlQueryArgs Additional query arguments to append to the request URL. * - * @return {JSX.Element} The rendered server-side content. + * @return The rendered server-side content. */ export function ServerSideRenderWithPostId( { urlQueryArgs = EMPTY_OBJECT, ...props -} ) { +}: ServerSideRenderWithPostIdProps ) { const currentPostId = useSelect( ( select ) => { // FIXME: @wordpress/server-side-render should not depend on @wordpress/editor. // It is used by blocks that can be loaded into a *non-post* block editor. // eslint-disable-next-line @wordpress/data-no-store-string-literals - const postId = select( 'core/editor' )?.getCurrentPostId(); + const editorStore = select( 'core/editor' ) as + | { + getCurrentPostId?: () => number | string | null; + } + | undefined; + const postId = editorStore?.getCurrentPostId?.(); // For templates and template parts we use a custom ID format. // Since they aren't real posts, we don't want to use their ID diff --git a/packages/server-side-render/src/test/index.js b/packages/server-side-render/src/test/index.ts similarity index 100% rename from packages/server-side-render/src/test/index.js rename to packages/server-side-render/src/test/index.ts diff --git a/packages/server-side-render/tsconfig.json b/packages/server-side-render/tsconfig.json new file mode 100644 index 00000000000000..5e8e37d4882b74 --- /dev/null +++ b/packages/server-side-render/tsconfig.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": [ "gutenberg-env", "gutenberg-test-env", "jest" ] + }, + "references": [ + { "path": "../api-fetch" }, + { "path": "../components" }, + { "path": "../compose" }, + { "path": "../data" }, + { "path": "../deprecated" }, + { "path": "../element" }, + { "path": "../i18n" }, + { "path": "../url" } + ], + "exclude": [ + "src/**/*.android.js", + "src/**/*.ios.js", + "src/**/*.native.js", + "src/**/react-native-*", + "src/**/stories/**/*.js", + "src/**/test/**/*.js" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 458315ef44a289..aa7907e04d1624 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -51,6 +51,7 @@ { "path": "packages/report-flaky-tests" }, { "path": "packages/rich-text" }, { "path": "packages/router" }, + { "path": "packages/server-side-render" }, { "path": "packages/shortcode" }, { "path": "packages/style-engine" }, { "path": "packages/sync" }, From 800a0d457969aa2dc3e9b5f1f651b7d7ab1b9a91 Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Fri, 15 May 2026 23:10:24 +0530 Subject: [PATCH 2/4] Update README and TypeScript definitions for ServerSideRender component - Enhanced README with detailed prop descriptions for ServerSideRender. - Updated TypeScript types in hook.ts and server-side-render.tsx for better type safety and clarity. - Improved error handling in useServerSideRender function. --- packages/server-side-render/README.md | 2 +- packages/server-side-render/src/hook.ts | 19 +++++--- .../src/server-side-render.tsx | 48 +++++-------------- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/packages/server-side-render/README.md b/packages/server-side-render/README.md index 3fbb541e8a617f..78658c64184eb7 100644 --- a/packages/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -68,7 +68,7 @@ _Parameters_ _Returns_ -- `React.JSX.Element`: The rendered server-side content. +- The rendered server-side content. ### useServerSideRender diff --git a/packages/server-side-render/src/hook.ts b/packages/server-side-render/src/hook.ts index 991b8f3ec385bd..24116794a41a10 100644 --- a/packages/server-side-render/src/hook.ts +++ b/packages/server-side-render/src/hook.ts @@ -6,7 +6,6 @@ import { useEffect, useState, useRef } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; -// @ts-expect-error __experimentalSanitizeBlockAttributes is not typed import { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks'; export function rendererPath( @@ -22,7 +21,9 @@ export function rendererPath( } export function removeBlockSupportAttributes( - attributes: Record< string, unknown > + attributes: Record< string, unknown > & { + style?: Record< string, unknown >; + } ): Record< string, unknown > { const { backgroundColor, @@ -43,7 +44,7 @@ export function removeBlockSupportAttributes( spacing, typography, ...restStyles - } = ( attributes?.style as Record< string, unknown > ) || {}; + } = attributes?.style || {}; return { ...restAttributes, @@ -173,15 +174,21 @@ export function useServerSideRender( content: res ? res.rendered : '', } ); } ) - .catch( ( error: Error ) => { + .catch( ( error: unknown ) => { // The request was aborted, do not update the response. - if ( error.name === 'AbortError' ) { + if ( + error instanceof Error && + error.name === 'AbortError' + ) { return; } setResponse( { status: 'error', - error: error.message, + error: + error instanceof Error + ? error.message + : String( error ), } ); } ) .finally( () => { diff --git a/packages/server-side-render/src/server-side-render.tsx b/packages/server-side-render/src/server-side-render.tsx index f631fc0a9cdd2d..3ca0b4831321ef 100644 --- a/packages/server-side-render/src/server-side-render.tsx +++ b/packages/server-side-render/src/server-side-render.tsx @@ -1,18 +1,7 @@ -/** - * External dependencies - */ -import type { ComponentType, ReactNode } from 'react'; - /** * WordPress dependencies */ -import { - RawHTML, - useEffect, - useState, - useRef, - useMemo, -} from '@wordpress/element'; +import { RawHTML, useEffect, useState, useMemo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { Placeholder, Spinner } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -34,7 +23,7 @@ interface ErrorPlaceholderProps extends PlaceholderProps { } interface LoadingPlaceholderProps { - children?: ReactNode; + children?: React.ReactNode; } function DefaultEmptyResponsePlaceholder( { className }: PlaceholderProps ) { @@ -52,7 +41,7 @@ function DefaultErrorResponsePlaceholder( { const errorMessage = sprintf( // translators: %s: error message describing the problem __( 'Error loading block: %s' ), - message || 'Unknown error' + message || __( 'Unknown error' ) ); return { errorMessage }; } @@ -94,13 +83,13 @@ function DefaultLoadingResponsePlaceholder( { interface ServerSideRenderProps extends UseServerSideRenderArgs { className?: string; - EmptyResponsePlaceholder?: ComponentType< PlaceholderProps >; - ErrorResponsePlaceholder?: ComponentType< ErrorPlaceholderProps >; - LoadingResponsePlaceholder?: ComponentType< LoadingPlaceholderProps >; + EmptyResponsePlaceholder?: React.ComponentType< PlaceholderProps >; + ErrorResponsePlaceholder?: React.ComponentType< ErrorPlaceholderProps >; + LoadingResponsePlaceholder?: React.ComponentType< LoadingPlaceholderProps >; } export function ServerSideRender( props: ServerSideRenderProps ) { - const prevContentRef = useRef( '' ); + const [ prevContent, setPrevContent ] = useState( '' ); const { className, EmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder, @@ -114,17 +103,15 @@ export function ServerSideRender( props: ServerSideRenderProps ) { // Store the previous successful HTML response to show while loading. useEffect( () => { if ( content ) { - prevContentRef.current = content; + setPrevContent( content ); } }, [ content ] ); if ( status === 'loading' ) { return ( - { !! prevContentRef.current && ( - - { prevContentRef.current } - + { !! prevContent && ( + { prevContent } ) } ); @@ -170,18 +157,9 @@ interface ServerSideRenderWithPostIdProps * } * ``` * - * @param {Object} props Component props. - * @param {string} props.block The identifier of the block to be serverside rendered. - * @param {Object} props.attributes The block attributes to be sent to the server for rendering. - * @param {string} [props.className] Additional classes to apply to the wrapper element. - * @param {string} [props.httpMethod='GET'] The HTTP method to use ('GET' or 'POST'). Default is 'GET' - * @param {Object} [props.urlQueryArgs] Additional query arguments to append to the request URL. - * @param {boolean} [props.skipBlockSupportAttributes=false] Whether to remove block support attributes before sending. - * @param {Function} [props.EmptyResponsePlaceholder] Component rendered when the API response is empty. - * @param {Function} [props.ErrorResponsePlaceholder] Component rendered when the API response is an error. - * @param {Function} [props.LoadingResponsePlaceholder] Component rendered while the API request is loading. - * - * @return {React.JSX.Element} The rendered server-side content. + * @param props Component props. + * @param props.urlQueryArgs Additional query arguments to append to the request URL. + * @return The rendered server-side content. */ export function ServerSideRenderWithPostId( { urlQueryArgs = EMPTY_OBJECT, From 35fdf48de2eeeb92cf7069303515306c736239c4 Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Thu, 28 May 2026 18:43:20 +0530 Subject: [PATCH 3/4] refactor: Migrate ServerSideRender component to TypeScript and enhance type definitions --- .../src/server-side-render.tsx | 61 ++++++++----------- packages/server-side-render/src/types.ts | 33 ++++++++++ packages/server-side-render/tsconfig.json | 9 +-- 3 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 packages/server-side-render/src/types.ts diff --git a/packages/server-side-render/src/server-side-render.tsx b/packages/server-side-render/src/server-side-render.tsx index 3ca0b4831321ef..676ed45f92fb10 100644 --- a/packages/server-side-render/src/server-side-render.tsx +++ b/packages/server-side-render/src/server-side-render.tsx @@ -1,7 +1,13 @@ /** * WordPress dependencies */ -import { RawHTML, useEffect, useState, useMemo } from '@wordpress/element'; +import { + RawHTML, + useEffect, + useState, + useMemo, + useRef, +} from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { Placeholder, Spinner } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -10,22 +16,16 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { useServerSideRender } from './hook'; -import type { UseServerSideRenderArgs } from './hook'; +import type { + PlaceholderProps, + ErrorPlaceholderProps, + LoadingPlaceholderProps, + ServerSideRenderProps, + ServerSideRenderWithPostIdProps, +} from './types'; const EMPTY_OBJECT = {}; -interface PlaceholderProps { - className?: string; -} - -interface ErrorPlaceholderProps extends PlaceholderProps { - message?: string; -} - -interface LoadingPlaceholderProps { - children?: React.ReactNode; -} - function DefaultEmptyResponsePlaceholder( { className }: PlaceholderProps ) { return ( @@ -81,15 +81,8 @@ function DefaultLoadingResponsePlaceholder( { ); } -interface ServerSideRenderProps extends UseServerSideRenderArgs { - className?: string; - EmptyResponsePlaceholder?: React.ComponentType< PlaceholderProps >; - ErrorResponsePlaceholder?: React.ComponentType< ErrorPlaceholderProps >; - LoadingResponsePlaceholder?: React.ComponentType< LoadingPlaceholderProps >; -} - export function ServerSideRender( props: ServerSideRenderProps ) { - const [ prevContent, setPrevContent ] = useState( '' ); + const prevContentRef = useRef( '' ); const { className, EmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder, @@ -103,15 +96,21 @@ export function ServerSideRender( props: ServerSideRenderProps ) { // Store the previous successful HTML response to show while loading. useEffect( () => { if ( content ) { - setPrevContent( content ); + prevContentRef.current = content; } }, [ content ] ); if ( status === 'loading' ) { return ( - { !! prevContent && ( - { prevContent } + { /* eslint-disable-next-line react-hooks/refs */ } + { !! prevContentRef.current && ( + + { + // eslint-disable-next-line react-hooks/refs + prevContentRef.current + } + ) } ); @@ -128,11 +127,6 @@ export function ServerSideRender( props: ServerSideRenderProps ) { return { content || '' }; } -interface ServerSideRenderWithPostIdProps - extends Omit< ServerSideRenderProps, 'urlQueryArgs' > { - urlQueryArgs?: Record< string, unknown >; -} - /** * A component that renders server-side content for blocks. * @@ -169,11 +163,8 @@ export function ServerSideRenderWithPostId( { // FIXME: @wordpress/server-side-render should not depend on @wordpress/editor. // It is used by blocks that can be loaded into a *non-post* block editor. // eslint-disable-next-line @wordpress/data-no-store-string-literals - const editorStore = select( 'core/editor' ) as - | { - getCurrentPostId?: () => number | string | null; - } - | undefined; + const editorStore = select( 'core/editor' ); + // @ts-expect-error - editorStore is not typed in @wordpress/data const postId = editorStore?.getCurrentPostId?.(); // For templates and template parts we use a custom ID format. diff --git a/packages/server-side-render/src/types.ts b/packages/server-side-render/src/types.ts new file mode 100644 index 00000000000000..5cd686fc3e8bfd --- /dev/null +++ b/packages/server-side-render/src/types.ts @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import type React from 'react'; + +/** + * Internal dependencies + */ +import type { UseServerSideRenderArgs } from './hook'; + +export interface PlaceholderProps { + className?: string; +} + +export interface ErrorPlaceholderProps extends PlaceholderProps { + message?: string; +} + +export interface LoadingPlaceholderProps { + children?: React.ReactNode; +} + +export interface ServerSideRenderProps extends UseServerSideRenderArgs { + className?: string; + EmptyResponsePlaceholder?: React.ComponentType< PlaceholderProps >; + ErrorResponsePlaceholder?: React.ComponentType< ErrorPlaceholderProps >; + LoadingResponsePlaceholder?: React.ComponentType< LoadingPlaceholderProps >; +} + +export interface ServerSideRenderWithPostIdProps + extends Omit< ServerSideRenderProps, 'urlQueryArgs' > { + urlQueryArgs?: Record< string, unknown >; +} diff --git a/packages/server-side-render/tsconfig.json b/packages/server-side-render/tsconfig.json index 5e8e37d4882b74..02ca31143328e6 100644 --- a/packages/server-side-render/tsconfig.json +++ b/packages/server-side-render/tsconfig.json @@ -6,6 +6,7 @@ }, "references": [ { "path": "../api-fetch" }, + { "path": "../blocks" }, { "path": "../components" }, { "path": "../compose" }, { "path": "../data" }, @@ -13,13 +14,5 @@ { "path": "../element" }, { "path": "../i18n" }, { "path": "../url" } - ], - "exclude": [ - "src/**/*.android.js", - "src/**/*.ios.js", - "src/**/*.native.js", - "src/**/react-native-*", - "src/**/stories/**/*.js", - "src/**/test/**/*.js" ] } From f07fe19c9172072c8355aa152ae1902106bdd16a Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Thu, 28 May 2026 19:21:19 +0530 Subject: [PATCH 4/4] refactor: enhance type definitions for ServerSideRender component and include types in package.json --- packages/server-side-render/package.json | 2 ++ packages/server-side-render/src/types.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index e019dec1887d9d..b6d54d8f5a165e 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -34,6 +34,7 @@ "module": "build-module/index.mjs", "exports": { ".": { + "types": "./build-types/index.d.ts", "import": "./build-module/index.mjs", "require": "./build/index.cjs" }, @@ -42,6 +43,7 @@ "react-native": "src/index", "wpScript": true, "wpScriptDefaultExport": true, + "types": "build-types/index.d.ts", "dependencies": { "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/blocks": "file:../blocks", diff --git a/packages/server-side-render/src/types.ts b/packages/server-side-render/src/types.ts index 5cd686fc3e8bfd..97a24b9b2ff571 100644 --- a/packages/server-side-render/src/types.ts +++ b/packages/server-side-render/src/types.ts @@ -9,10 +9,12 @@ import type React from 'react'; import type { UseServerSideRenderArgs } from './hook'; export interface PlaceholderProps { + /** Additional classes to apply to the wrapper element. */ className?: string; } export interface ErrorPlaceholderProps extends PlaceholderProps { + /** Error message describing the problem. */ message?: string; } @@ -21,13 +23,18 @@ export interface LoadingPlaceholderProps { } export interface ServerSideRenderProps extends UseServerSideRenderArgs { + /** Additional classes to apply to the wrapper element. */ className?: string; + /** Component rendered when the API response is empty. */ EmptyResponsePlaceholder?: React.ComponentType< PlaceholderProps >; + /** Component rendered when the API response is an error. */ ErrorResponsePlaceholder?: React.ComponentType< ErrorPlaceholderProps >; + /** Component rendered while the API request is loading. */ LoadingResponsePlaceholder?: React.ComponentType< LoadingPlaceholderProps >; } export interface ServerSideRenderWithPostIdProps extends Omit< ServerSideRenderProps, 'urlQueryArgs' > { + /** Additional query arguments to append to the request URL. */ urlQueryArgs?: Record< string, unknown >; }