Add block error boundary to JS-rendered blocks (https://github.com/woocommerce/woocommerce-blocks/pull/1166)

* Add block error boundary to All Products and Reviews blocks

* Add block error boundary to Price Filter and Attributes Filter blocks

* Add image

* Make block error component use props instead of hardcoded values

* Add props to BlockErrorBoundary

* Change 'text' prop to 'content'

* Add docs to proptypes

* Replace 'content' prop with 'text'
This commit is contained in:
Albert Juhé Lluveras 2019-11-15 15:15:55 +01:00 committed by GitHub
parent 2fde5d6890
commit 0739e4c536
11 changed files with 2932 additions and 29 deletions

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 296 KiB

View File

@ -0,0 +1,64 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { WC_BLOCKS_ASSET_URL } from '@woocommerce/block-settings';
import PropTypes from 'prop-types';
const BlockError = ( {
imageUrl = `${ WC_BLOCKS_ASSET_URL }img/block-error.svg`,
header = __( 'Oops!', 'woo-gutenberg-products-block' ),
text = __(
'There was an error with loading this content.',
'woo-gutenberg-products-block'
),
errorMessage,
} ) => {
return (
<div className="wc-block-error">
{ imageUrl && (
<img
className="wc-block-error__image"
src={ imageUrl }
alt=""
/>
) }
<div className="wc-block-error__content">
{ header && (
<p className="wc-block-error__header">{ header }</p>
) }
{ text && <p className="wc-block-error__text">{ text }</p> }
{ errorMessage && (
<p className="wc-block-error__message">{ errorMessage }</p>
) }
</div>
</div>
);
};
BlockError.propTypes = {
/**
* Error message to display below the content.
*/
errorMessage: PropTypes.string,
/**
* Text to display as the heading of the error block.
* If it's `null` or an empty string, no header will be displayed.
* If it's not defined, the default header will be used.
*/
header: PropTypes.string,
/**
* URL of the image to display.
* If it's `null` or an empty string, no image will be displayed.
* If it's not defined, the default image will be used.
*/
imageUrl: PropTypes.string,
/**
* Text to display in the error block below the header.
* If it's `null` or an empty string, nothing will be displayed.
* If it's not defined, the default text will be used.
*/
text: PropTypes.string,
};
export default BlockError;

View File

@ -0,0 +1,68 @@
/**
* External dependencies
*/
import { Component } from 'react';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import BlockError from './block-error';
import './style.scss';
class BlockErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError( error ) {
return { errorMessage: error.message, hasError: true };
}
render() {
const { header, imageUrl, showErrorMessage, text } = this.props;
const { errorMessage, hasError } = this.state;
if ( hasError ) {
return (
<BlockError
errorMessage={ showErrorMessage ? errorMessage : null }
header={ header }
imageUrl={ imageUrl }
text={ text }
/>
);
}
return this.props.children;
}
}
BlockErrorBoundary.propTypes = {
/**
* Text to display as the heading of the error block.
* If it's `null` or an empty string, no header will be displayed.
* If it's not defined, the default header will be used.
*/
header: PropTypes.string,
/**
* URL of the image to display.
* If it's `null` or an empty string, no image will be displayed.
* If it's not defined, the default image will be used.
*/
imageUrl: PropTypes.string,
/**
* Whether to display the JS error message.
*/
showErrorMessage: PropTypes.bool,
/**
* Text to display in the error block below the header.
* If it's `null` or an empty string, nothing will be displayed.
* If it's not defined, the default text will be used.
*/
text: PropTypes.string,
};
BlockErrorBoundary.defaultProps = {
showErrorMessage: false,
};
export default BlockErrorBoundary;

View File

@ -0,0 +1,30 @@
.wc-block-error {
display: flex;
background-color: #f3f3f4;
border-left: 4px solid #6d6d6d;
padding: $gap-larger $gap;
align-items: center;
justify-content: center;
flex-direction: column;
}
.wc-block-error__header {
font-size: 2em;
font-weight: bold;
margin: 0;
}
.wc-block-error__text,
.wc-block-error__message {
margin: 0;
}
@include breakpoint( ">480px" ) {
.wc-block-error {
flex-direction: row;
}
.wc-block-error__image + .wc-block-error__content {
margin-left: $gap;
}
}

View File

@ -15,6 +15,7 @@ import {
} from '@wordpress/element';
import { sortBy } from 'lodash';
import CheckboxList from '@woocommerce/base-components/checkbox-list';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
/**
* Internal dependencies
@ -173,7 +174,7 @@ const AttributeFilterBlock = ( { attributes, isPreview = false } ) => {
const TagName = `h${ attributes.headingLevel }`;
return (
<Fragment>
<BlockErrorBoundary>
{ ! isPreview && attributes.heading && (
<TagName>{ attributes.heading }</TagName>
) }
@ -185,7 +186,7 @@ const AttributeFilterBlock = ( { attributes, isPreview = false } ) => {
isLoading={ attributeTermsLoading }
/>
</div>
</Fragment>
</BlockErrorBoundary>
);
};

View File

@ -6,9 +6,10 @@ import {
useQueryStateByKey,
useQueryStateByContext,
} from '@woocommerce/base-hooks';
import { useCallback, Fragment } from '@wordpress/element';
import { useCallback } from '@wordpress/element';
import PriceSlider from '@woocommerce/base-components/price-slider';
import { CURRENCY } from '@woocommerce/settings';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
/**
* Component displaying a price filter.
@ -62,7 +63,7 @@ const PriceFilterBlock = ( { attributes, isPreview = false } ) => {
const TagName = `h${ attributes.headingLevel }`;
return (
<Fragment>
<BlockErrorBoundary>
{ ! isPreview && attributes.heading && (
<TagName>{ attributes.heading }</TagName>
) }
@ -81,7 +82,7 @@ const PriceFilterBlock = ( { attributes, isPreview = false } ) => {
isLoading={ isLoading }
/>
</div>
</Fragment>
</BlockErrorBoundary>
);
};

View File

@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import ProductListContainer from '@woocommerce/base-components/product-list/container';
import { InnerBlockConfigurationProvider } from '@woocommerce/base-context/inner-block-configuration-context';
import { ProductLayoutContextProvider } from '@woocommerce/base-context/product-layout-context';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
const layoutContextConfig = {
layoutStyleClassPrefix: 'wc-block-grid',
@ -33,14 +34,16 @@ class Block extends Component {
* wc-block-{$this->block_name},
*/
return (
<InnerBlockConfigurationProvider value={ parentBlockConfig }>
<ProductLayoutContextProvider value={ layoutContextConfig }>
<ProductListContainer
attributes={ attributes }
urlParameterSuffix={ urlParameterSuffix }
/>
</ProductLayoutContextProvider>
</InnerBlockConfigurationProvider>
<BlockErrorBoundary>
<InnerBlockConfigurationProvider value={ parentBlockConfig }>
<ProductLayoutContextProvider value={ layoutContextConfig }>
<ProductListContainer
attributes={ attributes }
urlParameterSuffix={ urlParameterSuffix }
/>
</ProductLayoutContextProvider>
</InnerBlockConfigurationProvider>
</BlockErrorBoundary>
);
}
}

View File

@ -11,6 +11,7 @@ import LoadMoreButton from '@woocommerce/base-components/load-more-button';
import ReviewList from '@woocommerce/base-components/review-list';
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
import withReviews from '@woocommerce/base-hocs/with-reviews';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
/**
* Block rendered in the editor.
@ -51,20 +52,26 @@ class EditorBlock extends Component {
}
return (
<Disabled>
{ attributes.showOrderby && ENABLE_REVIEW_RATING && (
<ReviewSortSelect readOnly value={ attributes.orderby } />
) }
<ReviewList attributes={ attributes } reviews={ reviews } />
{ attributes.showLoadMore && totalReviews > reviews.length && (
<LoadMoreButton
screenReaderLabel={ __(
'Load more reviews',
'woo-gutenberg-products-block'
<BlockErrorBoundary>
<Disabled>
{ attributes.showOrderby && ENABLE_REVIEW_RATING && (
<ReviewSortSelect
readOnly
value={ attributes.orderby }
/>
) }
<ReviewList attributes={ attributes } reviews={ reviews } />
{ attributes.showLoadMore &&
totalReviews > reviews.length && (
<LoadMoreButton
screenReaderLabel={ __(
'Load more reviews',
'woo-gutenberg-products-block'
) }
/>
) }
/>
) }
</Disabled>
</Disabled>
</BlockErrorBoundary>
);
}
}

View File

@ -2,13 +2,13 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Fragment } from 'react';
import PropTypes from 'prop-types';
import { ENABLE_REVIEW_RATING } from '@woocommerce/block-settings';
import LoadMoreButton from '@woocommerce/base-components/load-more-button';
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
import ReviewList from '@woocommerce/base-components/review-list';
import withReviews from '@woocommerce/base-hocs/with-reviews';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
/**
* Block rendered in the frontend.
@ -27,7 +27,7 @@ const FrontendBlock = ( {
}
return (
<Fragment>
<BlockErrorBoundary>
{ attributes.showOrderby !== 'false' && ENABLE_REVIEW_RATING && (
<ReviewSortSelect
defaultValue={ orderby }
@ -45,7 +45,7 @@ const FrontendBlock = ( {
) }
/>
) }
</Fragment>
</BlockErrorBoundary>
);
};

View File

@ -22,3 +22,4 @@ export const HAS_TAGS = getSetting( 'hasTags', true );
export const HOME_URL = getSetting( 'homeUrl', '' );
export const PRODUCT_COUNT = getSetting( 'productCount', 0 );
export const ATTRIBUTES = getSetting( 'attributes', [] );
export const WC_BLOCKS_ASSET_URL = getSetting( 'wcBlocksAssetUrl', '' );

View File

@ -117,6 +117,7 @@ class Assets {
'enableReviewRating' => 'yes' === get_option( 'woocommerce_enable_review_rating' ),
'productCount' => array_sum( (array) $product_counts ),
'attributes' => wc_get_attribute_taxonomies(),
'wcBlocksAssetUrl' => plugins_url( 'assets/', __DIR__ ),
]
);
}