Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callbacks to ServerSideRenderer to handle failures with custom renderers #16512

Merged

Conversation

mikejolley
Copy link
Contributor

@mikejolley mikejolley commented Jul 10, 2019

Description

Added 3 optional callbacks to the ServerSideRender component so that consumers can change what is displayed based on the result. These include:

  • onEmptyResponse -> emptyResponsePlaceholder
  • onErrorResponse -> errorResponsePlaceholder
  • onNullResponse -> loadingResponsePlaceholder

How has this been tested?

Testing this with my own code/callback function.

<ServerSideRender block={ name } attributes={ attributes } emptyResponsePlaceholder={ this.emptyResponsePlaceholder } />

And my callback:

	emptyResponsePlaceholder() {
		return (
			<Placeholder
				icon="category"
				label={ __( 'Products by Category', 'woo-gutenberg-products-block' ) }
				className="wc-block-products-grid wc-block-products-category"
			>
				{ __( 'No products were found that matched your selection.', 'woo-gutenberg-products-block' ) }
			</Placeholder>
		);
	}

Screenshots

The above example let's me render this when my block renderer returns empty content:

Edit Post ‹ local wordpress test — WordPress 2019-07-10 16-27-25

The default state is left alone if you don't pass a callback:

Edit Post ‹ local wordpress test — WordPress 2019-07-10 16-29-16

Types of changes

This adds some non-required props to <ServerSideRender> so it shouldn't impact existing code.

Checklist:

  • My code follows the WordPress code style.
  • My code follows the accessibility standards.

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution @mikejolley 👍 Generally, it seems a good idea to add options to configure empty/error states.

if ( response === '' ) {
return (
return !! onEmptyResponse ? onEmptyResponse( this ) : (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can onEmptyResponse be just a react element, instead of a function ? e.g: !! onEmptyResponse ? onEmptyResponse : (

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the RSS Block we want to disable the block settings, if we get an error from the SSR. So I think a function is more flexible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also thought function would be more flexible but open to change if it's required to get this through. Worth mentioning, the code using this component has no access to this components state without a function (passing this) which may be important, or not :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case @Soean provided is interesting:

In the RSS Block we want to disable the block settings.

We call the function onEmptyResponse on the render phase, render can not have side effects. So, for example, if there is a need to change the local state when the response is empty, that's not possible.
I guess to disable block settings on empty response, we would need to change a local state flag, so this change would not fit the RSS block use case.
I guess needing side effects when the response is empty will be common.
Maybe a possibility that would fit the use case would be an onEmptyResponse event. This function would be called, after the fetch is complete inside the fetch function, with an event object we define as a parameter. As a response to this event, users can apply any side effect desired e.g.: change state. This function would not render anything.
For the render, we have some possibilities:
- have a prop emptyResponse that is rendered when the response is empty.
- render nothing if onEmptyResponse is passed, the parent as a response to this event would be responsible for rendering the UI needed.

Any thoughts on this @mikejolley, @Soean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've no thoughts on this one as my use case didn't require changing state, just acting on the state the block already had.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In looking at the comments here I like some of what @jorgefilipecosta proposes. Here's what I'd like to see that I think will satisfy potential requirements for handling side-effects and a custom empty response render

  • Instead of an empty response event callback fired on completed fetch (as suggested by Jorge), I think it may be more extensible to simply fire any fetchComplete callback that is provided in props. This opens up the possibility for components to trigger side-effects on various fetch states (empty response, successful response, caught fetch error). This could be available for more advanced use-cases.
  • Just accept render props for the different response state props (and since these are placeholders, let's explicitly call them that) EmptyResponsePlaceholder, NullResponsePlaceholder and ErrorResponsePlaceholder options here and feed any state to them as they are rendererd. You can then have default values for the render props (which could simplify some of the conditionals here).

if ( response === '' ) {
return (
return !! onEmptyResponse ? onEmptyResponse( this ) : (
<Placeholder
className={ className }
>
{ __( 'Block rendered as empty.' ) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if onEmptyResponse should be rendered inside the placeholder? The advantage is that it is easier to use, one does not need to pass the placeholder. The disadvantage is that it is not as customizable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way so we can pass a custom classname to Placeholder, or allow flexibility to use something completely different.

<Placeholder
className={ className }
>
{ __( 'Block rendered as empty.' ) }
</Placeholder>
);
} else if ( ! response ) {
return (
return !! onNullResponse ? onNullResponse( this ) : (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the relevant use case for a null response? Can onEmptyResponse handle all falsy values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code, it seems the ! response is ran when response is null. This seems to happen when things have not yet loaded? It shows a spinner. I was thinking by allowing each of these to have their own callback you could render a custom loading state too.

Does this need revising @jorgefilipecosta ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya it looks like null is used to indicate a "loading" state. So there's value in leaving it as is. However, I would suggest a slight name change here to make the public exposed api a bit clearer. So instead of NullResponse for the render prop (continuing my earlier comment for the review), maybe make it LoadingResponsePlaceholder.

@Soean Soean added the [Package] Server Side Render /packages/server-side-render label Jul 10, 2019
@mikejolley
Copy link
Contributor Author

mikejolley commented Aug 13, 2019

@nerrad @jorgefilipecosta I've updated this with the suggested callbacks:

  • emptyResponsePlaceholder
  • errorResponsePlaceholder
  • loadingResponsePlaceholder

The defaultProps handle the default placeholders which keeps the code in render() clean.

Let me know if this is looking better. I've also updated the PR in Woo which is using this (woocommerce/woocommerce-blocks#717).

{ __( 'Block rendered as empty.' ) }
</Placeholder>
);
return emptyResponsePlaceholder( this.props, response );
Copy link
Contributor

@nerrad nerrad Aug 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a callback. Just make it a render prop. So consumers would provide the component they want rendered here and it would be implemented via:

<emptyResponsePlaceholder response={ response } { ...this.props } />

or more conventionally via (which is more similar to what you have):

emptyResponsePlaceholder( { ...this.props, response } );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look now - I just made this change. I renamed the props because the component names required a capital.

Copy link
Contributor

@nerrad nerrad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍 cc @jorgefilipecosta can you give this another looksee too?

@nerrad nerrad force-pushed the update/server-side-render-callbacks branch from 677e495 to aa339de Compare August 22, 2019 16:07
@nerrad nerrad merged commit be1c8ab into WordPress:master Aug 22, 2019
@nerrad nerrad added the [Type] Enhancement A suggestion for improvement. label Aug 22, 2019
donmhico pushed a commit to donmhico/gutenberg that referenced this pull request Aug 27, 2019
…nderers (WordPress#16512)

* Add callbacks to ServerSideRenderer to handle failures

* Update variable naming and set default renderers

* LoadingResponsePlaceholder

* swtich case

* fetchComplete

* Remove fetchcomplete method

* Pass render props

* add changelog entry

* add documentation for new props
gziolo pushed a commit that referenced this pull request Aug 29, 2019
…nderers (#16512)

* Add callbacks to ServerSideRenderer to handle failures

* Update variable naming and set default renderers

* LoadingResponsePlaceholder

* swtich case

* fetchComplete

* Remove fetchcomplete method

* Pass render props

* add changelog entry

* add documentation for new props
gziolo pushed a commit that referenced this pull request Aug 29, 2019
…nderers (#16512)

* Add callbacks to ServerSideRenderer to handle failures

* Update variable naming and set default renderers

* LoadingResponsePlaceholder

* swtich case

* fetchComplete

* Remove fetchcomplete method

* Pass render props

* add changelog entry

* add documentation for new props
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Server Side Render /packages/server-side-render [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants