diff --git a/pkg/webui/console/views/application-api-key-add/index.js b/pkg/webui/console/views/application-api-key-add/index.js index 7ddfa3da6d..244bc56310 100644 --- a/pkg/webui/console/views/application-api-key-add/index.js +++ b/pkg/webui/console/views/application-api-key-add/index.js @@ -18,13 +18,13 @@ import bind from 'autobind-decorator' import { connect } from 'react-redux' import { replace } from 'connected-react-router' -import Spinner from '../../../components/spinner' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import sharedMessages from '../../../lib/shared-messages' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import { ApiKeyCreateForm } from '../../components/api-key-form' +import withRequest from '../../../lib/components/with-request' import { getApplicationsRightsList } from '../../store/actions/applications' import { @@ -43,7 +43,15 @@ import api from '../../api' error: selectApplicationRightsError(state), rights: selectApplicationRights(state), universalRights: selectApplicationUniversalRights(state), +}), +dispatch => ({ + getApplicationsRightsList: appId => dispatch(getApplicationsRightsList(appId)), + navigateToList: appId => dispatch(replace(`/console/applications/${appId}/api-keys`)), })) +@withRequest( + ({ appId, getApplicationsRightsList }) => getApplicationsRightsList(appId), + ({ fetching, rights }) => fetching || !Boolean(rights.length) +) @withBreadcrumb('apps.single.api-keys.add', function (props) { const appId = props.appId return ( @@ -63,28 +71,14 @@ export default class ApplicationApiKeyAdd extends React.Component { this.createApplicationKey = key => api.application.apiKeys.create(props.appId, key) } - componentDidMount () { - const { dispatch, appId } = this.props - - dispatch(getApplicationsRightsList(appId)) - } - handleApprove () { - const { dispatch, appId } = this.props + const { navigateToList, appId } = this.props - dispatch(replace(`/console/applications/${appId}/api-keys`)) + navigateToList(appId) } render () { - const { rights, fetching, error, universalRights } = this.props - - if (error) { - throw error - } - - if (fetching || !rights.length) { - return - } + const { rights, universalRights } = this.props return ( diff --git a/pkg/webui/console/views/application-api-key-edit/index.js b/pkg/webui/console/views/application-api-key-edit/index.js index 127417d5fa..eb0ce455aa 100644 --- a/pkg/webui/console/views/application-api-key-edit/index.js +++ b/pkg/webui/console/views/application-api-key-edit/index.js @@ -21,10 +21,10 @@ import { replace } from 'connected-react-router' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import sharedMessages from '../../../lib/shared-messages' -import Spinner from '../../../components/spinner' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import { ApiKeyEditForm } from '../../components/api-key-form' +import withRequest from '../../../lib/components/with-request' import { getApplicationApiKey, @@ -62,12 +62,16 @@ import api from '../../api' } }, dispatch => ({ - async loadPageData (appId, apiKeyId) { - await dispatch(getApplicationsRightsList(appId)) + loadData (appId, apiKeyId) { + dispatch(getApplicationsRightsList(appId)) dispatch(getApplicationApiKey(appId, apiKeyId)) }, deleteSuccess: appId => dispatch(replace(`/console/applications/${appId}/api-keys`)), })) +@withRequest( + ({ loadData, appId, keyId }) => loadData(appId, keyId), + ({ fetching, apiKey }) => fetching || !Boolean(apiKey) +) @withBreadcrumb('apps.single.api-keys.edit', function (props) { const { appId, keyId } = props @@ -93,12 +97,6 @@ export default class ApplicationApiKeyEdit extends React.Component { ) } - componentDidMount () { - const { loadPageData, appId, keyId } = this.props - - loadPageData(appId, keyId) - } - onDeleteSuccess () { const { appId, deleteSuccess } = this.props @@ -106,15 +104,7 @@ export default class ApplicationApiKeyEdit extends React.Component { } render () { - const { apiKey, rights, fetching, error, universalRights } = this.props - - if (error) { - throw error - } - - if (fetching || !apiKey) { - return - } + const { apiKey, rights, universalRights } = this.props return ( diff --git a/pkg/webui/console/views/application-collaborator-add/index.js b/pkg/webui/console/views/application-collaborator-add/index.js index d7567e8b37..fd5edde00d 100644 --- a/pkg/webui/console/views/application-collaborator-add/index.js +++ b/pkg/webui/console/views/application-collaborator-add/index.js @@ -18,13 +18,13 @@ import bind from 'autobind-decorator' import { connect } from 'react-redux' import { push } from 'connected-react-router' -import Spinner from '../../../components/spinner' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import sharedMessages from '../../../lib/shared-messages' import CollaboratorForm from '../../components/collaborator-form' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' +import withRequest from '../../../lib/components/with-request' import { getApplicationsRightsList } from '../../store/actions/applications' import { @@ -54,6 +54,10 @@ import api from '../../api' redirectToList: () => dispatchProps.redirectToList(stateProps.appId), getApplicationsRightsList: () => dispatchProps.getApplicationsRightsList(stateProps.appId), })) +@withRequest( + ({ getApplicationsRightsList }) => getApplicationsRightsList(), + ({ fetching, rights }) => fetching || !Boolean(rights.length) +) @withBreadcrumb('apps.single.collaborators.add', function (props) { const appId = props.appId return ( @@ -71,39 +75,19 @@ export default class ApplicationCollaboratorAdd extends React.Component { error: '', } - componentDidMount () { - const { getApplicationsRightsList } = this.props - - getApplicationsRightsList() - } - - componentDidUpdate (prevProps) { - const { error } = this.props - - if (Boolean(error) && prevProps.error !== error) { - throw error - } - } - async handleSubmit (collaborator) { const { appId } = this.props await api.application.collaborators.add(appId, collaborator) - } render () { const { rights, - fetching, universalRights, redirectToList, } = this.props - if (fetching && !rights.length) { - return - } - return ( diff --git a/pkg/webui/console/views/application-collaborator-edit/index.js b/pkg/webui/console/views/application-collaborator-edit/index.js index 2e9cb9b67e..35dcc77512 100644 --- a/pkg/webui/console/views/application-collaborator-edit/index.js +++ b/pkg/webui/console/views/application-collaborator-edit/index.js @@ -22,10 +22,10 @@ import { withBreadcrumb } from '../../../components/breadcrumbs/context' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import sharedMessages from '../../../lib/shared-messages' import CollaboratorForm from '../../components/collaborator-form' -import Spinner from '../../../components/spinner' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import toast from '../../../components/toast' +import withRequest from '../../../lib/components/with-request' import { getApplicationCollaborator, @@ -84,6 +84,10 @@ import api from '../../api' redirectToList: () => dispatchProps.redirectToList(stateProps.appId), }) ) +@withRequest( + ({ loadData }) => loadData(), + ({ fetching, collaborator }) => fetching || !Boolean(collaborator) +) @withBreadcrumb('apps.single.collaborators.edit', function (props) { const { appId, collaboratorId, collaboratorType } = props @@ -102,20 +106,6 @@ export default class ApplicationCollaboratorEdit extends React.Component { error: '', } - componentDidMount () { - const { loadData } = this.props - - loadData() - } - - componentDidUpdate (prevProps) { - const { error } = this.props - - if (Boolean(error) && prevProps.error !== error) { - throw error - } - } - async handleSubmit (updatedCollaborator) { const { appId } = this.props @@ -158,15 +148,10 @@ export default class ApplicationCollaboratorEdit extends React.Component { const { collaborator, rights, - fetching, universalRights, redirectToList, } = this.props - if (fetching || !collaborator) { - return - } - return ( diff --git a/pkg/webui/console/views/application-integration-edit/index.js b/pkg/webui/console/views/application-integration-edit/index.js index ad78b58381..15c56a1f2a 100644 --- a/pkg/webui/console/views/application-integration-edit/index.js +++ b/pkg/webui/console/views/application-integration-edit/index.js @@ -25,9 +25,9 @@ import IntlHelmet from '../../../lib/components/intl-helmet' import Message from '../../../lib/components/message' import WebhookForm from '../../components/webhook-form' import toast from '../../../components/toast' -import Spinner from '../../../components/spinner' import diff from '../../../lib/diff' import sharedMessages from '../../../lib/shared-messages' +import withRequest from '../../../lib/components/with-request' import { selectSelectedWebhook, @@ -70,6 +70,10 @@ const webhookEntitySelector = [ navigateToList: () => dispatch(replace(`/console/applications/${appId}/integrations`)), } }) +@withRequest( + ({ getWebhook }) => getWebhook(), + ({ fetching, webhook }) => fetching || !Boolean(webhook) +) @withBreadcrumb('apps.single.integrations.edit', function (props) { const { appId, match: { params: { webhookId }}} = props return ( @@ -82,11 +86,6 @@ const webhookEntitySelector = [ }) @bind export default class ApplicationIntegrationEdit extends Component { - componentDidMount () { - const { getWebhook } = this.props - - getWebhook() - } async handleSubmit (webhook) { const { appId, match: { params: { webhookId }}, webhook: originalWebhook } = this.props @@ -120,15 +119,7 @@ export default class ApplicationIntegrationEdit extends Component { } render () { - const { webhook, fetching, error } = this.props - - if (fetching || !webhook) { - return - } - - if (error) { - throw error - } + const { webhook } = this.props return ( diff --git a/pkg/webui/console/views/gateway-api-key-add/index.js b/pkg/webui/console/views/gateway-api-key-add/index.js index dd15503221..113e287c90 100644 --- a/pkg/webui/console/views/gateway-api-key-add/index.js +++ b/pkg/webui/console/views/gateway-api-key-add/index.js @@ -18,13 +18,13 @@ import bind from 'autobind-decorator' import { connect } from 'react-redux' import { replace } from 'connected-react-router' -import Spinner from '../../../components/spinner' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import sharedMessages from '../../../lib/shared-messages' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import { ApiKeyCreateForm } from '../../components/api-key-form' +import withRequest from '../../../lib/components/with-request' import { getGatewaysRightsList } from '../../store/actions/gateways' import { @@ -43,7 +43,15 @@ import api from '../../api' error: selectGatewayRightsError(state), rights: selectGatewayRights(state), universalRights: selectGatewayUniversalRights(state), +}), +dispatch => ({ + getGatewaysRightsList: gtwId => dispatch(getGatewaysRightsList(gtwId)), + navigateToList: gtwId => dispatch(replace(`/console/gateways/${gtwId}/api-keys`)), })) +@withRequest( + ({ gtwId, getGatewaysRightsList }) => getGatewaysRightsList(gtwId), + ({ fetching, rights }) => fetching || !Boolean(rights.length) +) @withBreadcrumb('gtws.single.api-keys.add', function (props) { const gtwId = props.gtwId @@ -64,28 +72,14 @@ export default class GatewayApiKeyAdd extends React.Component { this.createGatewayKey = key => api.gateway.apiKeys.create(props.gtwId, key) } - componentDidMount () { - const { dispatch, gtwId } = this.props - - dispatch(getGatewaysRightsList(gtwId)) - } - handleApprove () { - const { dispatch, gtwId } = this.props + const { navigateToList, gtwId } = this.props - dispatch(replace(`/console/gateways/${gtwId}/api-keys`)) + navigateToList(gtwId) } render () { - const { rights, fetching, error, universalRights } = this.props - - if (error) { - throw error - } - - if (fetching || !rights.length) { - return - } + const { rights, universalRights } = this.props return ( diff --git a/pkg/webui/console/views/gateway-api-key-edit/index.js b/pkg/webui/console/views/gateway-api-key-edit/index.js index 8d75816d63..77cf78b3b8 100644 --- a/pkg/webui/console/views/gateway-api-key-edit/index.js +++ b/pkg/webui/console/views/gateway-api-key-edit/index.js @@ -21,10 +21,10 @@ import { replace } from 'connected-react-router' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import sharedMessages from '../../../lib/shared-messages' -import Spinner from '../../../components/spinner' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import { ApiKeyEditForm } from '../../components/api-key-form' +import withRequest from '../../../lib/components/with-request' import { getGatewayApiKey, @@ -61,12 +61,16 @@ import api from '../../api' } }, dispatch => ({ - async loadPageData (gtwId, apiKeyId) { - await dispatch(getGatewaysRightsList(gtwId)) + loadData (gtwId, apiKeyId) { + dispatch(getGatewaysRightsList(gtwId)) dispatch(getGatewayApiKey(gtwId, apiKeyId)) }, deleteSuccess: gtwId => dispatch(replace(`/console/gateways/${gtwId}/api-keys`)), })) +@withRequest( + ({ gtwId, keyId, loadData }) => loadData(gtwId, keyId), + ({ fetching, apiKey }) => fetching || !Boolean(apiKey) +) @withBreadcrumb('gtws.single.api-keys.edit', function (props) { const { gtwId, keyId } = props @@ -92,12 +96,6 @@ export default class GatewayApiKeyEdit extends React.Component { ) } - componentDidMount () { - const { loadPageData, gtwId, keyId } = this.props - - loadPageData(gtwId, keyId) - } - onDeleteSuccess () { const { gtwId, deleteSuccess } = this.props @@ -105,15 +103,7 @@ export default class GatewayApiKeyEdit extends React.Component { } render () { - const { apiKey, rights, fetching, error, universalRights } = this.props - - if (error) { - throw error - } - - if (fetching || !apiKey) { - return - } + const { apiKey, rights, universalRights } = this.props return ( diff --git a/pkg/webui/console/views/gateway-collaborator-add/index.js b/pkg/webui/console/views/gateway-collaborator-add/index.js index 3ee902e661..08aa923d98 100644 --- a/pkg/webui/console/views/gateway-collaborator-add/index.js +++ b/pkg/webui/console/views/gateway-collaborator-add/index.js @@ -18,13 +18,13 @@ import bind from 'autobind-decorator' import { connect } from 'react-redux' import { push } from 'connected-react-router' -import Spinner from '../../../components/spinner' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import { withBreadcrumb } from '../../../components/breadcrumbs/context' import sharedMessages from '../../../lib/shared-messages' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import CollaboratorForm from '../../components/collaborator-form' +import withRequest from '../../../lib/components/with-request' import { selectSelectedGatewayId, @@ -56,6 +56,10 @@ import api from '../../api' getGatewaysRightsList: () => dispatchProps.getGatewaysRightsList(stateProps.gtwId), redirectToList: () => dispatchProps.redirectToList(stateProps.gtwId), })) +@withRequest( + ({ getGatewaysRightsList }) => getGatewaysRightsList(), + ({ fetching, rights }) => fetching || !Boolean(rights.length) +) @withBreadcrumb('gtws.single.collaborators.add', function (props) { const gtwId = props.gtwId return ( @@ -73,20 +77,6 @@ export default class GatewayCollaboratorAdd extends React.Component { error: '', } - componentDidMount () { - const { getGatewaysRightsList } = this.props - - getGatewaysRightsList() - } - - componentDidUpdate (prevProps) { - const { error } = this.props - - if (Boolean(error) && prevProps.error !== error) { - throw error - } - } - handleSubmit (collaborator) { const { gtwId } = this.props @@ -96,15 +86,10 @@ export default class GatewayCollaboratorAdd extends React.Component { render () { const { rights, - fetching, redirectToList, universalRights, } = this.props - if (fetching && !rights.length) { - return - } - return ( diff --git a/pkg/webui/console/views/gateway-collaborator-edit/index.js b/pkg/webui/console/views/gateway-collaborator-edit/index.js index 932976972b..bc015c3d2f 100644 --- a/pkg/webui/console/views/gateway-collaborator-edit/index.js +++ b/pkg/webui/console/views/gateway-collaborator-edit/index.js @@ -22,10 +22,10 @@ import { withBreadcrumb } from '../../../components/breadcrumbs/context' import Breadcrumb from '../../../components/breadcrumbs/breadcrumb' import sharedMessages from '../../../lib/shared-messages' import CollaboratorForm from '../../components/collaborator-form' -import Spinner from '../../../components/spinner' import Message from '../../../lib/components/message' import IntlHelmet from '../../../lib/components/intl-helmet' import toast from '../../../components/toast' +import withRequest from '../../../lib/components/with-request' import { getGatewayCollaborator, @@ -85,6 +85,10 @@ import api from '../../api' ), redirectToList: () => dispatchProps.redirectToList(stateProps.gtwId), })) +@withRequest( + ({ loadData }) => loadData(), + ({ fetching, collaborator }) => fetching || !Boolean(collaborator) +) @withBreadcrumb('gtws.single.collaborators.edit', function (props) { const { gtwId, collaboratorId } = props @@ -103,20 +107,6 @@ export default class GatewayCollaboratorEdit extends React.Component { error: '', } - componentDidMount () { - const { loadData } = this.props - - loadData() - } - - componentDidUpdate (prevProps) { - const { error } = this.props - - if (Boolean(error) && prevProps.error !== error) { - throw error - } - } - handleSubmit (updatedCollaborator) { const { gtwId } = this.props @@ -140,15 +130,10 @@ export default class GatewayCollaboratorEdit extends React.Component { const { collaborator, rights, - fetching, redirectToList, universalRights, } = this.props - if (fetching || !collaborator) { - return - } - return ( diff --git a/pkg/webui/lib/components/with-request.js b/pkg/webui/lib/components/with-request.js new file mode 100644 index 0000000000..ab5435ed92 --- /dev/null +++ b/pkg/webui/lib/components/with-request.js @@ -0,0 +1,60 @@ +// Copyright © 2019 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react' + +import Spinner from '../../components/spinner' + +/** + * `withRequest` is a HOC that handles: + * * Requesting data on initial mount using the `loadData` prop. + * * Showing the loading spinner while the request in is progress using the `isFetchingTest` predicate. + * * Throwing an error received as the `error` prop. + * @param {Function} mapPropsToRequest - Selects the `request` given the wrapped component props. + * @param {Function} mapPropsToFetching - Selects the `fetching` value given the wrapped component props. + * If evaluates to `true`, then the loading spinner is rendered, otherwise renders the wrapped component. + * @param {Function} mapPropsToError - Selects the `error` value given the wrapped component props. + * @returns {Function} - An instance of the `withRequest` HOC. + */ +const withRequest = ( + mapPropsToRequest, + mapPropsToFetching = ({ fetching } = {}) => fetching, + mapPropsToError = ({ error } = {}) => error +) => Component => + class WithRequest extends React.Component { + + componentDidMount () { + mapPropsToRequest(this.props) + } + + componentDidUpdate (prevProps) { + const error = mapPropsToError(this.props) + const prevError = mapPropsToError(prevProps) + + // Check for errors only after component mounts and makes the request. + if (Boolean(error) && prevError !== error) { + throw error + } + } + + render () { + if (mapPropsToFetching(this.props)) { + return + } + + return + } + } + +export default withRequest