Skip to content

Commit 1bf6358

Browse files
Add error handling when there is an error to load stream posts (#107589)
1 parent b5bf500 commit 1bf6358

File tree

8 files changed

+134
-6
lines changed

8 files changed

+134
-6
lines changed

client/reader/stream/error.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { recordTracksEvent } from '@automattic/calypso-analytics';
2+
import { useTranslate } from 'i18n-calypso';
3+
import { useEffect } from 'react';
4+
import EmptyContent from 'calypso/components/empty-content';
5+
6+
/**
7+
* Props for the StreamError component.
8+
* @param onTryAgain - Callback to try again.
9+
* @param streamKey - The key of the stream.
10+
*/
11+
interface StreamErrorProps {
12+
onTryAgain?: () => void;
13+
streamKey: string;
14+
}
15+
16+
export const StreamError = ( { onTryAgain, streamKey }: StreamErrorProps ) => {
17+
const translate = useTranslate();
18+
19+
useEffect( () => {
20+
recordTracksEvent( 'reader_stream_error', {
21+
stream_key: streamKey,
22+
path: window.location.pathname,
23+
} );
24+
}, [ streamKey ] );
25+
26+
const handleTryAgain = () => {
27+
recordTracksEvent( 'reader_stream_error_try_again', {
28+
stream_key: streamKey,
29+
path: window.location.pathname,
30+
} );
31+
onTryAgain?.();
32+
};
33+
34+
return (
35+
<EmptyContent
36+
className="stream__empty"
37+
title={ translate( 'Sorry, something went wrong.' ) }
38+
line={ translate( 'We couldn’t load your feed. Please try again.' ) }
39+
action={ translate( 'Try again' ) }
40+
actionCallback={ handleTryAgain }
41+
/>
42+
);
43+
};

client/reader/stream/index.jsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import isNotificationsOpen from 'calypso/state/selectors/is-notifications-open';
5555
import { ReaderPerformanceTrackerStop } from '../reader-performance-tracker';
5656
import { CustomerCouncilBanner } from './customer-council-banner';
5757
import EmptyContent from './empty';
58+
import { StreamError } from './error';
5859
import PostLifecycle from './post-lifecycle';
5960
import PostPlaceholder from './post-placeholder';
6061

@@ -166,6 +167,10 @@ class ReaderStream extends Component {
166167
} );
167168
}
168169
}
170+
tryAgain = () => {
171+
this.props.clearStream( { streamKey: this.props.streamKey } );
172+
this.fetchNextPage( {} );
173+
};
169174

170175
focusSelectedPost = ( selectedPostKey ) => {
171176
const postRefKey = this.getPostRef( selectedPostKey );
@@ -647,7 +652,7 @@ class ReaderStream extends Component {
647652
isRequesting = true;
648653
}
649654

650-
const hasNoPosts = this.isMounted && items.length === 0 && ! isRequesting;
655+
const hasNoPosts = this.isMounted && items.length === 0 && ! isRequesting && ! this.props.error;
651656

652657
const streamType = getStreamType( streamKey );
653658

@@ -753,6 +758,16 @@ class ReaderStream extends Component {
753758

754759
const TopLevel = this.props.isMain ? ReaderMain : 'div';
755760

761+
if ( this.props.error ) {
762+
body = (
763+
<StreamError
764+
onTryAgain={ this.tryAgain }
765+
streamKey={ streamKey }
766+
context={ this.state.selectedTab }
767+
/>
768+
);
769+
}
770+
756771
return (
757772
<TopLevel className={ baseClassnames }>
758773
<div ref={ this.overlayRef } className="stream__init-overlay" />
@@ -812,6 +827,7 @@ export default connect(
812827
selectedPost,
813828
lastPage: stream.lastPage,
814829
isRequesting: stream.isRequesting,
830+
error: stream.error,
815831
shouldRequestRecs: shouldRequestRecs( state, streamKey, recsStreamKey ),
816832
likedPost: selectedPost && isLikedPost( state, selectedPost.site_ID, selectedPost.ID ),
817833
organizations: getReaderOrganizations( state ),

client/state/data-layer/wpcom/read/streams/index.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import {
1414
} from 'calypso/state/reader/action-types';
1515
import { receivePosts } from 'calypso/state/reader/posts/actions';
1616
import { receiveRecommendedSites } from 'calypso/state/reader/recommended-sites/actions';
17-
import { receivePage, receiveUpdates } from 'calypso/state/reader/streams/actions';
18-
19-
const noop = () => {};
17+
import {
18+
receivePage,
19+
receiveUpdates,
20+
receiveStreamError,
21+
} from 'calypso/state/reader/streams/actions';
2022

2123
/**
2224
* Pull the suffix off of a stream key
@@ -520,19 +522,23 @@ export function handlePage( action, data ) {
520522
return actions;
521523
}
522524

525+
const handleError = ( action, error ) => {
526+
return receiveStreamError( action, error );
527+
};
528+
523529
registerHandlers( 'state/data-layer/wpcom/read/streams/index.js', {
524530
[ READER_STREAMS_PAGE_REQUEST ]: [
525531
dispatchRequest( {
526532
fetch: requestPage,
527533
onSuccess: handlePage,
528-
onError: noop,
534+
onError: handleError,
529535
} ),
530536
],
531537
[ READER_STREAMS_PAGINATED_REQUEST ]: [
532538
dispatchRequest( {
533539
fetch: requestPage,
534540
onSuccess: handlePage,
535-
onError: noop,
541+
onError: handleError,
536542
} ),
537543
],
538544
} );

client/state/reader/action-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,4 @@ export const READER_USER_LISTS_REQUEST = 'READER_USER_LISTS_REQUEST';
111111
export const READER_USER_LISTS_RECEIVE = 'READER_USER_LISTS_RECEIVE';
112112
export const READER_VIEWING_FULL_POST_SET = 'READER_VIEWING_FULL_POST_SET';
113113
export const READER_VIEWING_FULL_POST_UNSET = 'READER_VIEWING_FULL_POST_UNSET';
114+
export const READER_STREAMS_ERROR = 'READER_STREAMS_ERROR';

client/state/reader/streams/actions.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
READER_STREAMS_NEW_POST_RECEIVE,
1212
READER_STREAMS_CLEAR,
1313
READER_STREAMS_REMOVE_ITEM,
14+
READER_STREAMS_ERROR,
1415
} from 'calypso/state/reader/action-types';
1516
import { getStream } from 'calypso/state/reader/streams/selectors';
1617
import 'calypso/state/data-layer/wpcom/read/streams';
@@ -155,3 +156,17 @@ export function requestPaginatedStream( { streamKey, page = 1, perPage = 10, loc
155156
},
156157
};
157158
}
159+
160+
/**
161+
* Returns an action object to signal that an error was encountered
162+
* when following a URL.
163+
* @param {Object} action Original action object
164+
* @param {Object} error Error object
165+
* @returns {Object} Action
166+
*/
167+
export function receiveStreamError( action, error ) {
168+
return {
169+
type: READER_STREAMS_ERROR,
170+
payload: { streamKey: action.payload.streamKey, error },
171+
};
172+
}

client/state/reader/streams/reducer.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
READER_STREAMS_CLEAR,
1414
READER_STREAMS_NEW_POST_RECEIVE,
1515
READER_STREAMS_REMOVE_ITEM,
16+
READER_STREAMS_ERROR,
1617
} from 'calypso/state/reader/action-types';
1718
import { keyedReducer, combineReducers } from 'calypso/state/utils';
1819
import { combineXPosts } from './utils';
@@ -311,6 +312,19 @@ export const pagination = ( state = { totalItems: 0, totalPages: 0 }, action ) =
311312
}
312313
};
313314

315+
export const error = ( state = null, action ) => {
316+
switch ( action.type ) {
317+
case READER_STREAMS_ERROR:
318+
return action.payload.error;
319+
case READER_STREAMS_CLEAR:
320+
case READER_STREAMS_PAGE_REQUEST:
321+
case READER_STREAMS_PAGINATED_REQUEST:
322+
return null;
323+
default:
324+
return state;
325+
}
326+
};
327+
314328
const streamReducer = combineReducers( {
315329
items,
316330
pendingItems,
@@ -319,6 +333,7 @@ const streamReducer = combineReducers( {
319333
isRequesting,
320334
pageHandle,
321335
pagination,
336+
error,
322337
} );
323338

324339
export default keyedReducer( 'payload.streamKey', streamReducer );

client/state/reader/streams/schema.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default {
1010
lastPage: { type: 'boolean' },
1111
isRequesting: { type: 'boolean' },
1212
pageHandle: { type: 'string' },
13+
error: { type: 'object' },
1314
},
1415
additionalProperties: false,
1516
},

client/state/reader/streams/test/reducer.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
selectNextItem,
99
selectPrevItem,
1010
requestPage,
11+
receiveStreamError,
12+
clearStream,
1113
} from '../actions';
1214
import {
1315
items,
@@ -16,6 +18,7 @@ import {
1618
pageHandle,
1719
isRequesting,
1820
lastPage,
21+
error,
1922
PENDING_ITEMS_DEFAULT,
2023
} from '../reducer';
2124

@@ -295,6 +298,34 @@ describe( 'streams.isRequesting', () => {
295298
} );
296299
} );
297300

301+
describe( 'streams.error', () => {
302+
it( 'should default to null', () => {
303+
expect( error( undefined, {} ) ).toBe( null );
304+
} );
305+
306+
it( 'should return the error', () => {
307+
const action = receiveStreamError(
308+
{ payload: { streamKey: 'following' } },
309+
new Error( 'test error' )
310+
);
311+
expect( error( undefined, action ) ).toEqual( new Error( 'test error' ) );
312+
} );
313+
314+
it( 'should cleanup the error after a page request', () => {
315+
const previousError = new Error( 'test error' );
316+
const action = requestPage( { streamKey: 'following' } );
317+
318+
expect( error( previousError, action ) ).toBe( null );
319+
} );
320+
321+
it( 'should cleanup the error after a stream is cleared', () => {
322+
const previousError = new Error( 'test error' );
323+
const action = clearStream( { streamKey: 'following' } );
324+
325+
expect( error( previousError, action ) ).toBe( null );
326+
} );
327+
} );
328+
298329
describe( 'streams.lastPage', () => {
299330
it( 'should default to false', () => {
300331
expect( lastPage( undefined, {} ) ).toBe( false );

0 commit comments

Comments
 (0)