@wordpress/server-side-render 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Install the module
18
18
  npm install @wordpress/server-side-render --save
19
19
  ```
20
20
 
21
- _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as IE browsers then using [core-js](https://github.com/zloirock/core-js) will add polyfills for these methods._
21
+ _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._
22
22
 
23
23
  ## Usage
24
24
 
@@ -58,6 +58,7 @@ The HTTP request method to use, either 'GET' or 'POST'. It's 'GET' by default. T
58
58
  - Required: No
59
59
 
60
60
  #### Example:
61
+
61
62
  ```php
62
63
  function add_rest_method( $endpoints ) {
63
64
  if ( is_wp_version_compatible( '5.5' ) ) {
@@ -108,9 +109,7 @@ The component is rendered while the API request is being processed (loading stat
108
109
 
109
110
  ```jsx
110
111
  const MyServerSideRender = () => (
111
- <ServerSideRender
112
- LoadingResponsePlaceholder={ MyAmazingPlaceholder }
113
- />
112
+ <ServerSideRender LoadingResponsePlaceholder={ MyAmazingPlaceholder } />
114
113
  );
115
114
  ```
116
115
 
@@ -63,11 +63,26 @@ function DefaultErrorResponsePlaceholder({
63
63
  }
64
64
 
65
65
  function DefaultLoadingResponsePlaceholder({
66
- className
66
+ children,
67
+ showLoader
67
68
  }) {
68
- return (0, _element.createElement)(_components.Placeholder, {
69
- className: className
70
- }, (0, _element.createElement)(_components.Spinner, null));
69
+ return (0, _element.createElement)("div", {
70
+ style: {
71
+ position: 'relative'
72
+ }
73
+ }, showLoader && (0, _element.createElement)("div", {
74
+ style: {
75
+ position: 'absolute',
76
+ top: '50%',
77
+ left: '50%',
78
+ marginTop: '-9px',
79
+ marginLeft: '-9px'
80
+ }
81
+ }, (0, _element.createElement)(_components.Spinner, null)), (0, _element.createElement)("div", {
82
+ style: {
83
+ opacity: showLoader ? '0.3' : 1
84
+ }
85
+ }, children));
71
86
  }
72
87
 
73
88
  function ServerSideRender(props) {
@@ -82,8 +97,10 @@ function ServerSideRender(props) {
82
97
  LoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder
83
98
  } = props;
84
99
  const isMountedRef = (0, _element.useRef)(true);
100
+ const [showLoader, setShowLoader] = (0, _element.useState)(false);
85
101
  const fetchRequestRef = (0, _element.useRef)();
86
102
  const [response, setResponse] = (0, _element.useState)(null);
103
+ const prevResponse = (0, _compose.usePrevious)(response);
87
104
  const prevProps = (0, _compose.usePrevious)(props);
88
105
 
89
106
  function fetchData() {
@@ -140,11 +157,31 @@ function ServerSideRender(props) {
140
157
  debouncedFetchData();
141
158
  }
142
159
  });
160
+ /**
161
+ * Effect to handle showing the loading placeholder.
162
+ * Show it only if there is no previous response or
163
+ * the request takes more than one second.
164
+ */
165
+
166
+ (0, _element.useEffect)(() => {
167
+ if (response !== null) {
168
+ return;
169
+ }
170
+
171
+ const timeout = setTimeout(() => {
172
+ setShowLoader(true);
173
+ }, 1000);
174
+ return () => clearTimeout(timeout);
175
+ }, [response]);
143
176
 
144
177
  if (response === '') {
145
178
  return (0, _element.createElement)(EmptyResponsePlaceholder, props);
146
179
  } else if (!response) {
147
- return (0, _element.createElement)(LoadingResponsePlaceholder, props);
180
+ return (0, _element.createElement)(LoadingResponsePlaceholder, (0, _extends2.default)({}, props, {
181
+ showLoader: !prevResponse || showLoader
182
+ }), !!prevResponse && (0, _element.createElement)(_element.RawHTML, {
183
+ className: className
184
+ }, prevResponse));
148
185
  } else if (response.error) {
149
186
  return (0, _element.createElement)(ErrorResponsePlaceholder, (0, _extends2.default)({
150
187
  response: response
@@ -1 +1 @@
1
- {"version":3,"sources":["@wordpress/server-side-render/src/server-side-render.js"],"names":["rendererPath","block","attributes","urlQueryArgs","context","DefaultEmptyResponsePlaceholder","className","DefaultErrorResponsePlaceholder","response","errorMessage","errorMsg","DefaultLoadingResponsePlaceholder","ServerSideRender","props","httpMethod","EmptyResponsePlaceholder","ErrorResponsePlaceholder","LoadingResponsePlaceholder","isMountedRef","fetchRequestRef","setResponse","prevProps","fetchData","current","sanitizedAttributes","isPostRequest","urlAttributes","path","data","fetchRequest","method","then","fetchResponse","rendered","catch","error","message","debouncedFetchData","undefined"],"mappings":"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","sourcesContent":["/**\n * External dependencies\n */\nimport { isEqual } from 'lodash';\n\n/**\n * WordPress dependencies\n */\nimport { useDebounce, usePrevious } from '@wordpress/compose';\nimport { RawHTML, useEffect, useRef, useState } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { Placeholder, Spinner } from '@wordpress/components';\nimport { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks';\n\nexport function rendererPath( block, attributes = null, urlQueryArgs = {} ) {\n\treturn addQueryArgs( `/wp/v2/block-renderer/${ block }`, {\n\t\tcontext: 'edit',\n\t\t...( null !== attributes ? { attributes } : {} ),\n\t\t...urlQueryArgs,\n\t} );\n}\n\nfunction DefaultEmptyResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t{ __( 'Block rendered as empty.' ) }\n\t\t</Placeholder>\n\t);\n}\n\nfunction DefaultErrorResponsePlaceholder( { response, className } ) {\n\tconst errorMessage = sprintf(\n\t\t// translators: %s: error message describing the problem\n\t\t__( 'Error loading block: %s' ),\n\t\tresponse.errorMsg\n\t);\n\treturn <Placeholder className={ className }>{ errorMessage }</Placeholder>;\n}\n\nfunction DefaultLoadingResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t<Spinner />\n\t\t</Placeholder>\n\t);\n}\n\nexport default function ServerSideRender( props ) {\n\tconst {\n\t\tattributes,\n\t\tblock,\n\t\tclassName,\n\t\thttpMethod = 'GET',\n\t\turlQueryArgs,\n\t\tEmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder,\n\t\tErrorResponsePlaceholder = DefaultErrorResponsePlaceholder,\n\t\tLoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder,\n\t} = props;\n\n\tconst isMountedRef = useRef( true );\n\tconst fetchRequestRef = useRef();\n\tconst [ response, setResponse ] = useState( null );\n\tconst prevProps = usePrevious( props );\n\n\tfunction fetchData() {\n\t\tif ( ! isMountedRef.current ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( null !== response ) {\n\t\t\tsetResponse( null );\n\t\t}\n\n\t\tconst sanitizedAttributes =\n\t\t\tattributes &&\n\t\t\t__experimentalSanitizeBlockAttributes( block, attributes );\n\n\t\t// If httpMethod is 'POST', send the attributes in the request body instead of the URL.\n\t\t// This allows sending a larger attributes object than in a GET request, where the attributes are in the URL.\n\t\tconst isPostRequest = 'POST' === httpMethod;\n\t\tconst urlAttributes = isPostRequest\n\t\t\t? null\n\t\t\t: sanitizedAttributes ?? null;\n\t\tconst path = rendererPath( block, urlAttributes, urlQueryArgs );\n\t\tconst data = isPostRequest\n\t\t\t? { attributes: sanitizedAttributes ?? null }\n\t\t\t: null;\n\n\t\t// Store the latest fetch request so that when we process it, we can\n\t\t// check if it is the current request, to avoid race conditions on slow networks.\n\t\tconst fetchRequest = ( fetchRequestRef.current = apiFetch( {\n\t\t\tpath,\n\t\t\tdata,\n\t\t\tmethod: isPostRequest ? 'POST' : 'GET',\n\t\t} )\n\t\t\t.then( ( fetchResponse ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current &&\n\t\t\t\t\tfetchResponse\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( fetchResponse.rendered );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.catch( ( error ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( {\n\t\t\t\t\t\terror: true,\n\t\t\t\t\t\terrorMsg: error.message,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} ) );\n\n\t\treturn fetchRequest;\n\t}\n\n\tconst debouncedFetchData = useDebounce( fetchData, 500 );\n\n\t// When the component unmounts, set isMountedRef to false. This will\n\t// let the async fetch callbacks know when to stop.\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect( () => {\n\t\t// Don't debounce the first fetch. This ensures that the first render\n\t\t// shows data as soon as possible\n\t\tif ( prevProps === undefined ) {\n\t\t\tfetchData();\n\t\t} else if ( ! isEqual( prevProps, props ) ) {\n\t\t\tdebouncedFetchData();\n\t\t}\n\t} );\n\n\tif ( response === '' ) {\n\t\treturn <EmptyResponsePlaceholder { ...props } />;\n\t} else if ( ! response ) {\n\t\treturn <LoadingResponsePlaceholder { ...props } />;\n\t} else if ( response.error ) {\n\t\treturn <ErrorResponsePlaceholder response={ response } { ...props } />;\n\t}\n\n\treturn <RawHTML className={ className }>{ response }</RawHTML>;\n}\n"]}
1
+ {"version":3,"sources":["@wordpress/server-side-render/src/server-side-render.js"],"names":["rendererPath","block","attributes","urlQueryArgs","context","DefaultEmptyResponsePlaceholder","className","DefaultErrorResponsePlaceholder","response","errorMessage","errorMsg","DefaultLoadingResponsePlaceholder","children","showLoader","position","top","left","marginTop","marginLeft","opacity","ServerSideRender","props","httpMethod","EmptyResponsePlaceholder","ErrorResponsePlaceholder","LoadingResponsePlaceholder","isMountedRef","setShowLoader","fetchRequestRef","setResponse","prevResponse","prevProps","fetchData","current","sanitizedAttributes","isPostRequest","urlAttributes","path","data","fetchRequest","method","then","fetchResponse","rendered","catch","error","message","debouncedFetchData","undefined","timeout","setTimeout","clearTimeout"],"mappings":"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","sourcesContent":["/**\n * External dependencies\n */\nimport { isEqual } from 'lodash';\n\n/**\n * WordPress dependencies\n */\nimport { useDebounce, usePrevious } from '@wordpress/compose';\nimport { RawHTML, useEffect, useRef, useState } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { Placeholder, Spinner } from '@wordpress/components';\nimport { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks';\n\nexport function rendererPath( block, attributes = null, urlQueryArgs = {} ) {\n\treturn addQueryArgs( `/wp/v2/block-renderer/${ block }`, {\n\t\tcontext: 'edit',\n\t\t...( null !== attributes ? { attributes } : {} ),\n\t\t...urlQueryArgs,\n\t} );\n}\n\nfunction DefaultEmptyResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t{ __( 'Block rendered as empty.' ) }\n\t\t</Placeholder>\n\t);\n}\n\nfunction DefaultErrorResponsePlaceholder( { response, className } ) {\n\tconst errorMessage = sprintf(\n\t\t// translators: %s: error message describing the problem\n\t\t__( 'Error loading block: %s' ),\n\t\tresponse.errorMsg\n\t);\n\treturn <Placeholder className={ className }>{ errorMessage }</Placeholder>;\n}\n\nfunction DefaultLoadingResponsePlaceholder( { children, showLoader } ) {\n\treturn (\n\t\t<div style={ { position: 'relative' } }>\n\t\t\t{ showLoader && (\n\t\t\t\t<div\n\t\t\t\t\tstyle={ {\n\t\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\t\ttop: '50%',\n\t\t\t\t\t\tleft: '50%',\n\t\t\t\t\t\tmarginTop: '-9px',\n\t\t\t\t\t\tmarginLeft: '-9px',\n\t\t\t\t\t} }\n\t\t\t\t>\n\t\t\t\t\t<Spinner />\n\t\t\t\t</div>\n\t\t\t) }\n\t\t\t<div style={ { opacity: showLoader ? '0.3' : 1 } }>\n\t\t\t\t{ children }\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default function ServerSideRender( props ) {\n\tconst {\n\t\tattributes,\n\t\tblock,\n\t\tclassName,\n\t\thttpMethod = 'GET',\n\t\turlQueryArgs,\n\t\tEmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder,\n\t\tErrorResponsePlaceholder = DefaultErrorResponsePlaceholder,\n\t\tLoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder,\n\t} = props;\n\n\tconst isMountedRef = useRef( true );\n\tconst [ showLoader, setShowLoader ] = useState( false );\n\tconst fetchRequestRef = useRef();\n\tconst [ response, setResponse ] = useState( null );\n\tconst prevResponse = usePrevious( response );\n\tconst prevProps = usePrevious( props );\n\n\tfunction fetchData() {\n\t\tif ( ! isMountedRef.current ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( null !== response ) {\n\t\t\tsetResponse( null );\n\t\t}\n\n\t\tconst sanitizedAttributes =\n\t\t\tattributes &&\n\t\t\t__experimentalSanitizeBlockAttributes( block, attributes );\n\n\t\t// If httpMethod is 'POST', send the attributes in the request body instead of the URL.\n\t\t// This allows sending a larger attributes object than in a GET request, where the attributes are in the URL.\n\t\tconst isPostRequest = 'POST' === httpMethod;\n\t\tconst urlAttributes = isPostRequest\n\t\t\t? null\n\t\t\t: sanitizedAttributes ?? null;\n\t\tconst path = rendererPath( block, urlAttributes, urlQueryArgs );\n\t\tconst data = isPostRequest\n\t\t\t? { attributes: sanitizedAttributes ?? null }\n\t\t\t: null;\n\n\t\t// Store the latest fetch request so that when we process it, we can\n\t\t// check if it is the current request, to avoid race conditions on slow networks.\n\t\tconst fetchRequest = ( fetchRequestRef.current = apiFetch( {\n\t\t\tpath,\n\t\t\tdata,\n\t\t\tmethod: isPostRequest ? 'POST' : 'GET',\n\t\t} )\n\t\t\t.then( ( fetchResponse ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current &&\n\t\t\t\t\tfetchResponse\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( fetchResponse.rendered );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.catch( ( error ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( {\n\t\t\t\t\t\terror: true,\n\t\t\t\t\t\terrorMsg: error.message,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} ) );\n\n\t\treturn fetchRequest;\n\t}\n\n\tconst debouncedFetchData = useDebounce( fetchData, 500 );\n\n\t// When the component unmounts, set isMountedRef to false. This will\n\t// let the async fetch callbacks know when to stop.\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect( () => {\n\t\t// Don't debounce the first fetch. This ensures that the first render\n\t\t// shows data as soon as possible\n\t\tif ( prevProps === undefined ) {\n\t\t\tfetchData();\n\t\t} else if ( ! isEqual( prevProps, props ) ) {\n\t\t\tdebouncedFetchData();\n\t\t}\n\t} );\n\n\t/**\n\t * Effect to handle showing the loading placeholder.\n\t * Show it only if there is no previous response or\n\t * the request takes more than one second.\n\t */\n\tuseEffect( () => {\n\t\tif ( response !== null ) {\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout( () => {\n\t\t\tsetShowLoader( true );\n\t\t}, 1000 );\n\t\treturn () => clearTimeout( timeout );\n\t}, [ response ] );\n\n\tif ( response === '' ) {\n\t\treturn <EmptyResponsePlaceholder { ...props } />;\n\t} else if ( ! response ) {\n\t\treturn (\n\t\t\t<LoadingResponsePlaceholder\n\t\t\t\t{ ...props }\n\t\t\t\tshowLoader={ ! prevResponse || showLoader }\n\t\t\t>\n\t\t\t\t{ !! prevResponse && (\n\t\t\t\t\t<RawHTML className={ className }>{ prevResponse }</RawHTML>\n\t\t\t\t) }\n\t\t\t</LoadingResponsePlaceholder>\n\t\t);\n\t} else if ( response.error ) {\n\t\treturn <ErrorResponsePlaceholder response={ response } { ...props } />;\n\t}\n\n\treturn <RawHTML className={ className }>{ response }</RawHTML>;\n}\n"]}
@@ -46,11 +46,26 @@ function DefaultErrorResponsePlaceholder({
46
46
  }
47
47
 
48
48
  function DefaultLoadingResponsePlaceholder({
49
- className
49
+ children,
50
+ showLoader
50
51
  }) {
51
- return createElement(Placeholder, {
52
- className: className
53
- }, createElement(Spinner, null));
52
+ return createElement("div", {
53
+ style: {
54
+ position: 'relative'
55
+ }
56
+ }, showLoader && createElement("div", {
57
+ style: {
58
+ position: 'absolute',
59
+ top: '50%',
60
+ left: '50%',
61
+ marginTop: '-9px',
62
+ marginLeft: '-9px'
63
+ }
64
+ }, createElement(Spinner, null)), createElement("div", {
65
+ style: {
66
+ opacity: showLoader ? '0.3' : 1
67
+ }
68
+ }, children));
54
69
  }
55
70
 
56
71
  export default function ServerSideRender(props) {
@@ -65,8 +80,10 @@ export default function ServerSideRender(props) {
65
80
  LoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder
66
81
  } = props;
67
82
  const isMountedRef = useRef(true);
83
+ const [showLoader, setShowLoader] = useState(false);
68
84
  const fetchRequestRef = useRef();
69
85
  const [response, setResponse] = useState(null);
86
+ const prevResponse = usePrevious(response);
70
87
  const prevProps = usePrevious(props);
71
88
 
72
89
  function fetchData() {
@@ -124,11 +141,31 @@ export default function ServerSideRender(props) {
124
141
  debouncedFetchData();
125
142
  }
126
143
  });
144
+ /**
145
+ * Effect to handle showing the loading placeholder.
146
+ * Show it only if there is no previous response or
147
+ * the request takes more than one second.
148
+ */
149
+
150
+ useEffect(() => {
151
+ if (response !== null) {
152
+ return;
153
+ }
154
+
155
+ const timeout = setTimeout(() => {
156
+ setShowLoader(true);
157
+ }, 1000);
158
+ return () => clearTimeout(timeout);
159
+ }, [response]);
127
160
 
128
161
  if (response === '') {
129
162
  return createElement(EmptyResponsePlaceholder, props);
130
163
  } else if (!response) {
131
- return createElement(LoadingResponsePlaceholder, props);
164
+ return createElement(LoadingResponsePlaceholder, _extends({}, props, {
165
+ showLoader: !prevResponse || showLoader
166
+ }), !!prevResponse && createElement(RawHTML, {
167
+ className: className
168
+ }, prevResponse));
132
169
  } else if (response.error) {
133
170
  return createElement(ErrorResponsePlaceholder, _extends({
134
171
  response: response
@@ -1 +1 @@
1
- {"version":3,"sources":["@wordpress/server-side-render/src/server-side-render.js"],"names":["isEqual","useDebounce","usePrevious","RawHTML","useEffect","useRef","useState","__","sprintf","apiFetch","addQueryArgs","Placeholder","Spinner","__experimentalSanitizeBlockAttributes","rendererPath","block","attributes","urlQueryArgs","context","DefaultEmptyResponsePlaceholder","className","DefaultErrorResponsePlaceholder","response","errorMessage","errorMsg","DefaultLoadingResponsePlaceholder","ServerSideRender","props","httpMethod","EmptyResponsePlaceholder","ErrorResponsePlaceholder","LoadingResponsePlaceholder","isMountedRef","fetchRequestRef","setResponse","prevProps","fetchData","current","sanitizedAttributes","isPostRequest","urlAttributes","path","data","fetchRequest","method","then","fetchResponse","rendered","catch","error","message","debouncedFetchData","undefined"],"mappings":"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","sourcesContent":["/**\n * External dependencies\n */\nimport { isEqual } from 'lodash';\n\n/**\n * WordPress dependencies\n */\nimport { useDebounce, usePrevious } from '@wordpress/compose';\nimport { RawHTML, useEffect, useRef, useState } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { Placeholder, Spinner } from '@wordpress/components';\nimport { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks';\n\nexport function rendererPath( block, attributes = null, urlQueryArgs = {} ) {\n\treturn addQueryArgs( `/wp/v2/block-renderer/${ block }`, {\n\t\tcontext: 'edit',\n\t\t...( null !== attributes ? { attributes } : {} ),\n\t\t...urlQueryArgs,\n\t} );\n}\n\nfunction DefaultEmptyResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t{ __( 'Block rendered as empty.' ) }\n\t\t</Placeholder>\n\t);\n}\n\nfunction DefaultErrorResponsePlaceholder( { response, className } ) {\n\tconst errorMessage = sprintf(\n\t\t// translators: %s: error message describing the problem\n\t\t__( 'Error loading block: %s' ),\n\t\tresponse.errorMsg\n\t);\n\treturn <Placeholder className={ className }>{ errorMessage }</Placeholder>;\n}\n\nfunction DefaultLoadingResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t<Spinner />\n\t\t</Placeholder>\n\t);\n}\n\nexport default function ServerSideRender( props ) {\n\tconst {\n\t\tattributes,\n\t\tblock,\n\t\tclassName,\n\t\thttpMethod = 'GET',\n\t\turlQueryArgs,\n\t\tEmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder,\n\t\tErrorResponsePlaceholder = DefaultErrorResponsePlaceholder,\n\t\tLoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder,\n\t} = props;\n\n\tconst isMountedRef = useRef( true );\n\tconst fetchRequestRef = useRef();\n\tconst [ response, setResponse ] = useState( null );\n\tconst prevProps = usePrevious( props );\n\n\tfunction fetchData() {\n\t\tif ( ! isMountedRef.current ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( null !== response ) {\n\t\t\tsetResponse( null );\n\t\t}\n\n\t\tconst sanitizedAttributes =\n\t\t\tattributes &&\n\t\t\t__experimentalSanitizeBlockAttributes( block, attributes );\n\n\t\t// If httpMethod is 'POST', send the attributes in the request body instead of the URL.\n\t\t// This allows sending a larger attributes object than in a GET request, where the attributes are in the URL.\n\t\tconst isPostRequest = 'POST' === httpMethod;\n\t\tconst urlAttributes = isPostRequest\n\t\t\t? null\n\t\t\t: sanitizedAttributes ?? null;\n\t\tconst path = rendererPath( block, urlAttributes, urlQueryArgs );\n\t\tconst data = isPostRequest\n\t\t\t? { attributes: sanitizedAttributes ?? null }\n\t\t\t: null;\n\n\t\t// Store the latest fetch request so that when we process it, we can\n\t\t// check if it is the current request, to avoid race conditions on slow networks.\n\t\tconst fetchRequest = ( fetchRequestRef.current = apiFetch( {\n\t\t\tpath,\n\t\t\tdata,\n\t\t\tmethod: isPostRequest ? 'POST' : 'GET',\n\t\t} )\n\t\t\t.then( ( fetchResponse ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current &&\n\t\t\t\t\tfetchResponse\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( fetchResponse.rendered );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.catch( ( error ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( {\n\t\t\t\t\t\terror: true,\n\t\t\t\t\t\terrorMsg: error.message,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} ) );\n\n\t\treturn fetchRequest;\n\t}\n\n\tconst debouncedFetchData = useDebounce( fetchData, 500 );\n\n\t// When the component unmounts, set isMountedRef to false. This will\n\t// let the async fetch callbacks know when to stop.\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect( () => {\n\t\t// Don't debounce the first fetch. This ensures that the first render\n\t\t// shows data as soon as possible\n\t\tif ( prevProps === undefined ) {\n\t\t\tfetchData();\n\t\t} else if ( ! isEqual( prevProps, props ) ) {\n\t\t\tdebouncedFetchData();\n\t\t}\n\t} );\n\n\tif ( response === '' ) {\n\t\treturn <EmptyResponsePlaceholder { ...props } />;\n\t} else if ( ! response ) {\n\t\treturn <LoadingResponsePlaceholder { ...props } />;\n\t} else if ( response.error ) {\n\t\treturn <ErrorResponsePlaceholder response={ response } { ...props } />;\n\t}\n\n\treturn <RawHTML className={ className }>{ response }</RawHTML>;\n}\n"]}
1
+ {"version":3,"sources":["@wordpress/server-side-render/src/server-side-render.js"],"names":["isEqual","useDebounce","usePrevious","RawHTML","useEffect","useRef","useState","__","sprintf","apiFetch","addQueryArgs","Placeholder","Spinner","__experimentalSanitizeBlockAttributes","rendererPath","block","attributes","urlQueryArgs","context","DefaultEmptyResponsePlaceholder","className","DefaultErrorResponsePlaceholder","response","errorMessage","errorMsg","DefaultLoadingResponsePlaceholder","children","showLoader","position","top","left","marginTop","marginLeft","opacity","ServerSideRender","props","httpMethod","EmptyResponsePlaceholder","ErrorResponsePlaceholder","LoadingResponsePlaceholder","isMountedRef","setShowLoader","fetchRequestRef","setResponse","prevResponse","prevProps","fetchData","current","sanitizedAttributes","isPostRequest","urlAttributes","path","data","fetchRequest","method","then","fetchResponse","rendered","catch","error","message","debouncedFetchData","undefined","timeout","setTimeout","clearTimeout"],"mappings":"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","sourcesContent":["/**\n * External dependencies\n */\nimport { isEqual } from 'lodash';\n\n/**\n * WordPress dependencies\n */\nimport { useDebounce, usePrevious } from '@wordpress/compose';\nimport { RawHTML, useEffect, useRef, useState } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { Placeholder, Spinner } from '@wordpress/components';\nimport { __experimentalSanitizeBlockAttributes } from '@wordpress/blocks';\n\nexport function rendererPath( block, attributes = null, urlQueryArgs = {} ) {\n\treturn addQueryArgs( `/wp/v2/block-renderer/${ block }`, {\n\t\tcontext: 'edit',\n\t\t...( null !== attributes ? { attributes } : {} ),\n\t\t...urlQueryArgs,\n\t} );\n}\n\nfunction DefaultEmptyResponsePlaceholder( { className } ) {\n\treturn (\n\t\t<Placeholder className={ className }>\n\t\t\t{ __( 'Block rendered as empty.' ) }\n\t\t</Placeholder>\n\t);\n}\n\nfunction DefaultErrorResponsePlaceholder( { response, className } ) {\n\tconst errorMessage = sprintf(\n\t\t// translators: %s: error message describing the problem\n\t\t__( 'Error loading block: %s' ),\n\t\tresponse.errorMsg\n\t);\n\treturn <Placeholder className={ className }>{ errorMessage }</Placeholder>;\n}\n\nfunction DefaultLoadingResponsePlaceholder( { children, showLoader } ) {\n\treturn (\n\t\t<div style={ { position: 'relative' } }>\n\t\t\t{ showLoader && (\n\t\t\t\t<div\n\t\t\t\t\tstyle={ {\n\t\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\t\ttop: '50%',\n\t\t\t\t\t\tleft: '50%',\n\t\t\t\t\t\tmarginTop: '-9px',\n\t\t\t\t\t\tmarginLeft: '-9px',\n\t\t\t\t\t} }\n\t\t\t\t>\n\t\t\t\t\t<Spinner />\n\t\t\t\t</div>\n\t\t\t) }\n\t\t\t<div style={ { opacity: showLoader ? '0.3' : 1 } }>\n\t\t\t\t{ children }\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default function ServerSideRender( props ) {\n\tconst {\n\t\tattributes,\n\t\tblock,\n\t\tclassName,\n\t\thttpMethod = 'GET',\n\t\turlQueryArgs,\n\t\tEmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder,\n\t\tErrorResponsePlaceholder = DefaultErrorResponsePlaceholder,\n\t\tLoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder,\n\t} = props;\n\n\tconst isMountedRef = useRef( true );\n\tconst [ showLoader, setShowLoader ] = useState( false );\n\tconst fetchRequestRef = useRef();\n\tconst [ response, setResponse ] = useState( null );\n\tconst prevResponse = usePrevious( response );\n\tconst prevProps = usePrevious( props );\n\n\tfunction fetchData() {\n\t\tif ( ! isMountedRef.current ) {\n\t\t\treturn;\n\t\t}\n\t\tif ( null !== response ) {\n\t\t\tsetResponse( null );\n\t\t}\n\n\t\tconst sanitizedAttributes =\n\t\t\tattributes &&\n\t\t\t__experimentalSanitizeBlockAttributes( block, attributes );\n\n\t\t// If httpMethod is 'POST', send the attributes in the request body instead of the URL.\n\t\t// This allows sending a larger attributes object than in a GET request, where the attributes are in the URL.\n\t\tconst isPostRequest = 'POST' === httpMethod;\n\t\tconst urlAttributes = isPostRequest\n\t\t\t? null\n\t\t\t: sanitizedAttributes ?? null;\n\t\tconst path = rendererPath( block, urlAttributes, urlQueryArgs );\n\t\tconst data = isPostRequest\n\t\t\t? { attributes: sanitizedAttributes ?? null }\n\t\t\t: null;\n\n\t\t// Store the latest fetch request so that when we process it, we can\n\t\t// check if it is the current request, to avoid race conditions on slow networks.\n\t\tconst fetchRequest = ( fetchRequestRef.current = apiFetch( {\n\t\t\tpath,\n\t\t\tdata,\n\t\t\tmethod: isPostRequest ? 'POST' : 'GET',\n\t\t} )\n\t\t\t.then( ( fetchResponse ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current &&\n\t\t\t\t\tfetchResponse\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( fetchResponse.rendered );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.catch( ( error ) => {\n\t\t\t\tif (\n\t\t\t\t\tisMountedRef.current &&\n\t\t\t\t\tfetchRequest === fetchRequestRef.current\n\t\t\t\t) {\n\t\t\t\t\tsetResponse( {\n\t\t\t\t\t\terror: true,\n\t\t\t\t\t\terrorMsg: error.message,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} ) );\n\n\t\treturn fetchRequest;\n\t}\n\n\tconst debouncedFetchData = useDebounce( fetchData, 500 );\n\n\t// When the component unmounts, set isMountedRef to false. This will\n\t// let the async fetch callbacks know when to stop.\n\tuseEffect(\n\t\t() => () => {\n\t\t\tisMountedRef.current = false;\n\t\t},\n\t\t[]\n\t);\n\n\tuseEffect( () => {\n\t\t// Don't debounce the first fetch. This ensures that the first render\n\t\t// shows data as soon as possible\n\t\tif ( prevProps === undefined ) {\n\t\t\tfetchData();\n\t\t} else if ( ! isEqual( prevProps, props ) ) {\n\t\t\tdebouncedFetchData();\n\t\t}\n\t} );\n\n\t/**\n\t * Effect to handle showing the loading placeholder.\n\t * Show it only if there is no previous response or\n\t * the request takes more than one second.\n\t */\n\tuseEffect( () => {\n\t\tif ( response !== null ) {\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout( () => {\n\t\t\tsetShowLoader( true );\n\t\t}, 1000 );\n\t\treturn () => clearTimeout( timeout );\n\t}, [ response ] );\n\n\tif ( response === '' ) {\n\t\treturn <EmptyResponsePlaceholder { ...props } />;\n\t} else if ( ! response ) {\n\t\treturn (\n\t\t\t<LoadingResponsePlaceholder\n\t\t\t\t{ ...props }\n\t\t\t\tshowLoader={ ! prevResponse || showLoader }\n\t\t\t>\n\t\t\t\t{ !! prevResponse && (\n\t\t\t\t\t<RawHTML className={ className }>{ prevResponse }</RawHTML>\n\t\t\t\t) }\n\t\t\t</LoadingResponsePlaceholder>\n\t\t);\n\t} else if ( response.error ) {\n\t\treturn <ErrorResponsePlaceholder response={ response } { ...props } />;\n\t}\n\n\treturn <RawHTML className={ className }>{ response }</RawHTML>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/server-side-render",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -27,19 +27,19 @@
27
27
  "react-native": "src/index",
28
28
  "dependencies": {
29
29
  "@babel/runtime": "^7.13.10",
30
- "@wordpress/api-fetch": "^5.2.2",
31
- "@wordpress/blocks": "^11.1.0",
32
- "@wordpress/components": "^17.0.0",
33
- "@wordpress/compose": "^5.0.2",
34
- "@wordpress/data": "^6.1.0",
35
- "@wordpress/deprecated": "^3.2.1",
36
- "@wordpress/element": "^4.0.1",
37
- "@wordpress/i18n": "^4.2.2",
38
- "@wordpress/url": "^3.2.2",
30
+ "@wordpress/api-fetch": "^5.2.3",
31
+ "@wordpress/blocks": "^11.1.1",
32
+ "@wordpress/components": "^18.0.0",
33
+ "@wordpress/compose": "^5.0.3",
34
+ "@wordpress/data": "^6.1.1",
35
+ "@wordpress/deprecated": "^3.2.2",
36
+ "@wordpress/element": "^4.0.2",
37
+ "@wordpress/i18n": "^4.2.3",
38
+ "@wordpress/url": "^3.2.3",
39
39
  "lodash": "^4.17.21"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
- "gitHead": "98c42a7187f788fe3e023f04df7f5dcbdae4e4e7"
44
+ "gitHead": "8f7f052bc04e3f4eb50f479ced14be1489b9fa79"
45
45
  }
@@ -39,11 +39,26 @@ function DefaultErrorResponsePlaceholder( { response, className } ) {
39
39
  return <Placeholder className={ className }>{ errorMessage }</Placeholder>;
40
40
  }
41
41
 
42
- function DefaultLoadingResponsePlaceholder( { className } ) {
42
+ function DefaultLoadingResponsePlaceholder( { children, showLoader } ) {
43
43
  return (
44
- <Placeholder className={ className }>
45
- <Spinner />
46
- </Placeholder>
44
+ <div style={ { position: 'relative' } }>
45
+ { showLoader && (
46
+ <div
47
+ style={ {
48
+ position: 'absolute',
49
+ top: '50%',
50
+ left: '50%',
51
+ marginTop: '-9px',
52
+ marginLeft: '-9px',
53
+ } }
54
+ >
55
+ <Spinner />
56
+ </div>
57
+ ) }
58
+ <div style={ { opacity: showLoader ? '0.3' : 1 } }>
59
+ { children }
60
+ </div>
61
+ </div>
47
62
  );
48
63
  }
49
64
 
@@ -60,8 +75,10 @@ export default function ServerSideRender( props ) {
60
75
  } = props;
61
76
 
62
77
  const isMountedRef = useRef( true );
78
+ const [ showLoader, setShowLoader ] = useState( false );
63
79
  const fetchRequestRef = useRef();
64
80
  const [ response, setResponse ] = useState( null );
81
+ const prevResponse = usePrevious( response );
65
82
  const prevProps = usePrevious( props );
66
83
 
67
84
  function fetchData() {
@@ -139,10 +156,34 @@ export default function ServerSideRender( props ) {
139
156
  }
140
157
  } );
141
158
 
159
+ /**
160
+ * Effect to handle showing the loading placeholder.
161
+ * Show it only if there is no previous response or
162
+ * the request takes more than one second.
163
+ */
164
+ useEffect( () => {
165
+ if ( response !== null ) {
166
+ return;
167
+ }
168
+ const timeout = setTimeout( () => {
169
+ setShowLoader( true );
170
+ }, 1000 );
171
+ return () => clearTimeout( timeout );
172
+ }, [ response ] );
173
+
142
174
  if ( response === '' ) {
143
175
  return <EmptyResponsePlaceholder { ...props } />;
144
176
  } else if ( ! response ) {
145
- return <LoadingResponsePlaceholder { ...props } />;
177
+ return (
178
+ <LoadingResponsePlaceholder
179
+ { ...props }
180
+ showLoader={ ! prevResponse || showLoader }
181
+ >
182
+ { !! prevResponse && (
183
+ <RawHTML className={ className }>{ prevResponse }</RawHTML>
184
+ ) }
185
+ </LoadingResponsePlaceholder>
186
+ );
146
187
  } else if ( response.error ) {
147
188
  return <ErrorResponsePlaceholder response={ response } { ...props } />;
148
189
  }