Skip to content

Commit

Permalink
LPS-102562 Simplifies state managemeent with useReducer and direct fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
jbalsas authored and brianchandotcom committed Nov 13, 2019
1 parent 66243ad commit 03f5671
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,167 +12,90 @@
* details.
*/

import {useResource} from '@clayui/data-provider';
import ClayLoadingIndicator from '@clayui/loading-indicator';
import {fetch, openToast} from 'frontend-js-web';
import {useTimeout} from 'frontend-js-react-web';
import {fetch, openToast} from 'frontend-js-web';
import PropTypes from 'prop-types';
import React, {useEffect, useRef, useState, useCallback} from 'react';
import React, {useCallback, useEffect, useReducer} from 'react';

/**
* Shows a toast notification.
*
* @param {boolean} error Flag indicating if is an error or not
* @protected
*/
function _showNotification(hasError) {
let message;

if (hasError) {
message = Liferay.Language.get('an-unexpected-error-occurred');
} else {
message = Liferay.Language.get('changes-saved');
}

const openToastParams = {
message
};

if (hasError) {
openToastParams.title = Liferay.Language.get('error');
openToastParams.type = 'danger';
}

openToast(openToastParams);
}
import reducer, {STATES} from './reducer.es';

/**
* Shows the bulk actions status
*/
function BulkStatus({
bulkInProgress = false,
bulkComponentId,
bulkInProgress,
bulkStatusUrl = '/bulk/v1.0/status',
intervalSpeed = 1000,
pathModule,
portletNamespace,
waitingTime = 1000
}) {
const delay = useTimeout();

const [isBulkInProgress, setIsBulkInProgress] = useState(bulkInProgress);
const [
isBulkLoadingIndicatorVisible,
setIsBulkLoadingIndicatorVisible
] = useState(false);
const isBulkHappened = useRef(false);
const [state, dispatch] = useReducer(
reducer,
bulkInProgress ? {current: STATES.LONG_POLLING} : {current: STATES.IDLE}
);

const statusCallback = useCallback(() => {
return delay(() => {
fetch(
`${Liferay.ThemeDisplay.getPortalURL()}${pathModule}${bulkStatusUrl}`
)
.then(response => response.json())
.then(response => {
if (response.actionInProgress) {
dispatch({type: 'check'});
} else {
dispatch({type: 'success'});
}
})
.catch(() => dispatch({type: 'error'}));
}, intervalSpeed);
}, [bulkStatusUrl, delay, pathModule, intervalSpeed]);

useEffect(() => {
let dispose;

if (waitingTime) {
if (isBulkInProgress && !isBulkLoadingIndicatorVisible) {
dispoe = delay(() => {
setIsBulkLoadingIndicatorVisible(true);
}, waitingTime);
}
}

return dispose;
}, [waitingTime, isBulkInProgress, isBulkLoadingIndicatorVisible]);

const [networkState, setNetworkState] = useState({
error: false,
loading: false,
networkStatus: 4,
unused: false
});

const refetchSafe = useCallback(() => refetch(), [refetch]);
if (state.current === STATES.SHORT_POLLING) {
statusCallback();

useEffect(() => {
if (!Liferay.component(`${portletNamespace}BulkStatus`)) {
Liferay.component(
`${portletNamespace}BulkStatus`,
{
startWatch: () => {
if (!isBulkInProgress) {
setIsBulkInProgress(true);
refetchSafe();
}
}
},
{
destroyOnNavigate: true
}
dispose = delay(
() => dispatch({type: 'initialDelayCompleted'}),
waitingTime
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const {refetch, resource} = useResource({
link: () => {
return fetch(
Liferay.ThemeDisplay.getPortalURL() + pathModule + bulkStatusUrl
).then(response => response.json());
},
onNetworkStatusChange: status =>
setNetworkState({
error: status === 5,
loading: status < 4,
networkStatus: status,
unused: status === 4
})
});
} else if (state.current === STATES.LONG_POLLING) {
statusCallback();
} else if (state.current === STATES.NOTIFY) {
openToast(state.toast);

useEffect(() => {
if (networkState.error) {
_showNotification(true);
dispatch({type: 'notificationCompleted'});
}
}, [networkState.error]);

useEffect(() => {
if (
networkState.unused &&
!isBulkInProgress &&
isBulkHappened.current
) {
_showNotification(false);
}
}, [isBulkHappened, isBulkInProgress, networkState.unused]);

useEffect(() => {
if (resource) {
if (resource.actionInProgress && !networkState.error) {
setIsBulkInProgress(true);
setTimeout(() => {
refetchSafe();
}, intervalSpeed);
if (isBulkHappened.current === false) {
isBulkHappened.current = true;
return dispose;
}, [delay, state, statusCallback, waitingTime]);

if (!Liferay.component(bulkComponentId)) {
Liferay.component(
bulkComponentId,
{
startWatch: () => {
dispatch({type: 'start'});
}
},
{
destroyOnNavigate: true
}

if (!resource.actionInProgress || networkState.error) {
setIsBulkInProgress(false);
setIsBulkLoadingIndicatorVisible(false);
}
}
}, [
intervalSpeed,
isBulkHappened,
networkState.error,
refetchSafe,
resource
]);
);
}

return (
<div className="bulk-status-container">
<div
className={`bulk-status ${!isBulkLoadingIndicatorVisible &&
'closed'}`}
>
<div className={`bulk-status ${!state.current.show && 'closed'}`}>
<div className="bulk-status-content">
<ClayLoadingIndicator light small />

<span>{Liferay.Language.get('processing-actions')}</span>
</div>
</div>
Expand All @@ -181,58 +104,10 @@ function BulkStatus({
}

BulkStatus.propTypes = {
/**
* Wether to show the component or not
* @type {Boolean}
*/
bulkInProgress: PropTypes.bool,

/**
* Uri to send the bulk status fetch request.
* @instance
* @memberof BulkStatus
* @type {String}
*/
bulkStatusUrl: PropTypes.string,

/**
* The interval (in milliseconds) on how often
* we will check if there are bulk actions in progress.
*
* @instance
* @memberof BulkStatus
* @type {Number}
*/
intervalSpeed: PropTypes.number,

/**
* PathModule
*
* @instance
* @memberof EditTags
* @review
* @type {String}
*/
pathModule: PropTypes.string.isRequired,

/**
* Portlet's namespace
*
* @instance
* @memberof BulkStatus
* @review
* @type {string}
*/
portletNamespace: PropTypes.string.isRequired,

/**
* The time (in milliseconds) we have to wait to
* show the component.
*
* @instance
* @memberof BulkStatus
* @type {Number}
*/
waitingTime: PropTypes.number
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/

const STATES = {
IDLE: {running: false, show: false},
LONG_POLLING: {running: true, show: true},
NOTIFY: {running: false, show: false},
SHORT_POLLING: {running: true, show: false}
};

const TOASTS = {
ERROR: {
message: Liferay.Language.get('an-unexpected-error-occurred'),
title: Liferay.Language.get('error'),
type: 'danger'
},
SUCCESS: {
message: Liferay.Language.get('changes-saved')
}
};

export {STATES};

export default function reducer(state, action) {
switch (action.type) {
case 'check':
if (state.current === STATES.LONG_POLLING) {
return {
...state,
timestamp: Date.now()
};
}
break;

case 'error':
return {
...state,
current: STATES.NOTIFY,
toast: TOASTS.ERROR
};

case 'initialDelayCompleted':
if (state.current === STATES.SHORT_POLLING) {
return {
...state,
current: STATES.LONG_POLLING,
timestamp: Date.now()
};
}

break;

case 'notificationCompleted':
if (state.current === STATES.NOTIFY) {
return {
...state,
current: STATES.IDLE
};
}

break;

case 'start':
if (state.current === STATES.IDLE) {
return {
...state,
current: STATES.SHORT_POLLING
};
}

break;

case 'success':
return {
...state,
current: STATES.NOTIFY,
toast: TOASTS.SUCCESS
};

default:
return state;
}

return state;
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ String navigation = ParamUtil.getString(request, "navigation");
Map<String, Object> context = new HashMap<>();
context.put("bulkComponentId", liferayPortletResponse.getNamespace() + "BulkStatus");
context.put("bulkInProgress", bulkSelectionRunner.isBusy(user));
context.put("pathModule", PortalUtil.getPathModule());
context.put("portletNamespace", liferayPortletResponse.getNamespace());
%>

<div id="<portlet:namespace/>bulkStatus-root">
<div>
<react:component
data="<%= context %>"
module="document_library/js/bulk/BulkStatus.es"
Expand Down

0 comments on commit 03f5671

Please sign in to comment.