Skip to content

Commit

Permalink
[Codebase improvement] Refine ItemsList base component (previously Li…
Browse files Browse the repository at this point in the history
…veItemsList) (#3415)
  • Loading branch information
kravets-levko authored Feb 18, 2019
1 parent 298fe6a commit d483785
Show file tree
Hide file tree
Showing 14 changed files with 687 additions and 387 deletions.
2 changes: 1 addition & 1 deletion .circleci/Dockerfile.cypress
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM cypress/browsers:chrome67
ENV APP /usr/src/app
WORKDIR $APP

RUN npm install --no-save puppeteer@1.10.0 cypress@^3.1.5 @percy/cypress@^0.2.3 > /dev/null
RUN npm install --no-save puppeteer@1.10.0 cypress@^3.1.5 @percy/cypress@^0.2.3 atob@2.1.2 > /dev/null

COPY cypress $APP/cypress
COPY cypress.json $APP/cypress.json
Expand Down
130 changes: 130 additions & 0 deletions client/app/components/items-list/ItemsList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { omit, debounce } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { $route } from '@/services/ng';
import { clientConfig } from '@/services/auth';
import { StateStorage } from './classes/StateStorage';

export const ControllerType = PropTypes.shape({
// values of props declared by wrapped component, current route's locals (`resolve: { ... }`) and title
params: PropTypes.object.isRequired,

isLoaded: PropTypes.bool.isRequired,
isEmpty: PropTypes.bool.isRequired,

// search
searchTerm: PropTypes.string,
updateSearch: PropTypes.func.isRequired, // (searchTerm: string) => void

// tags
selectedTags: PropTypes.array.isRequired,
updateSelectedTags: PropTypes.func.isRequired, // (selectedTags: array of tags) => void

// sorting
orderByField: PropTypes.string,
orderByReverse: PropTypes.bool.isRequired,
toggleSorting: PropTypes.func.isRequired, // (orderByField: string) => void

// pagination
page: PropTypes.number.isRequired,
itemsPerPage: PropTypes.number.isRequired,
totalItemsCount: PropTypes.number.isRequired,
pageSizeOptions: PropTypes.arrayOf(PropTypes.number).isRequired,
pageItems: PropTypes.array.isRequired,
updatePagination: PropTypes.func.isRequired, // ({ page: number, itemsPerPage: number }) => void

handleError: PropTypes.func.isRequired, // (error) => void
});

export function wrap(WrappedComponent, itemsSource, stateStorage) {
class ItemsListWrapper extends React.Component {
static propTypes = {
...omit(WrappedComponent.propTypes, ['controller']),
onError: PropTypes.func,
children: PropTypes.node,
};

static defaultProps = {
...omit(WrappedComponent.defaultProps, ['controller']),
onError: (error) => {
// Allow calling chain to roll up, and then throw the error in global context
setTimeout(() => { throw error; });
},
children: null,
};

constructor(props) {
super(props);

stateStorage = stateStorage || new StateStorage();
itemsSource.setState({ ...stateStorage.getState(), validate: false });
itemsSource.getCallbackContext = () => this.state;

itemsSource.onBeforeUpdate = () => {
const state = itemsSource.getState();
stateStorage.setState(state);
this.setState(this.getState({ ...state, isLoaded: false }));
};

itemsSource.onAfterUpdate = () => {
const state = itemsSource.getState();
this.setState(this.getState({ ...state, isLoaded: true }));
};

itemsSource.onError = error => this.props.onError(error);

const initialState = this.getState({ ...itemsSource.getState(), isLoaded: false });
const { updatePagination, toggleSorting, updateSearch, updateSelectedTags, update, handleError } = itemsSource;
this.state = {
...initialState,
toggleSorting, // eslint-disable-line react/no-unused-state
updateSearch: debounce(updateSearch, 200), // eslint-disable-line react/no-unused-state
updateSelectedTags, // eslint-disable-line react/no-unused-state
updatePagination, // eslint-disable-line react/no-unused-state
update, // eslint-disable-line react/no-unused-state
handleError, // eslint-disable-line react/no-unused-state
};
}

componentDidMount() {
this.state.update();
}

// eslint-disable-next-line class-methods-use-this
getState({ isLoaded, totalCount, pageItems, ...rest }) {
const params = {
// Add some properties of current route (`$resolve`, title)
// ANGULAR_REMOVE_ME Revisit when some React router will be used
title: $route.current.title,
...omit($route.current.locals, ['$scope', '$template']),

// Add to params all props except of own ones
...omit(this.props, ['onError', 'children']),
};
return {
...rest,

params,

isLoaded,
isEmpty: !isLoaded || (totalCount === 0),
totalItemsCount: isLoaded ? totalCount : 0,
pageSizeOptions: clientConfig.pageSizeOptions,
pageItems: isLoaded ? pageItems : [],
};
}

render() {
// don't pass own props to wrapped component
const { children, onError, ...props } = this.props;
props.controller = this.state;
return <WrappedComponent {...props}>{ children }</WrappedComponent>;
}
}

// Copy static methods from `WrappedComponent`
hoistNonReactStatics(ItemsListWrapper, WrappedComponent);

return ItemsListWrapper;
}
242 changes: 0 additions & 242 deletions client/app/components/items-list/LiveItemsList.jsx

This file was deleted.

Loading

0 comments on commit d483785

Please sign in to comment.