-
Notifications
You must be signed in to change notification settings - Fork 522
/
EntitiesList.js
160 lines (140 loc) · 5.44 KB
/
EntitiesList.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* @flow */
import React from 'react';
import { connect } from 'react-redux';
import InfiniteScroll from 'react-infinite-scroller';
import './EntitiesList.css';
import * as locales from 'core/locales';
import * as navigation from 'core/navigation';
import { actions, NAME } from '..';
import Entity from './Entity';
import { CircleLoader } from 'core/loaders'
import type { Locale } from 'core/locales';
import type { NavigationParams } from 'core/navigation';
import type { Entities, DbEntity } from '../reducer';
type Props = {|
entities: {|
entities: Entities,
hasMore: boolean,
fetching: boolean,
|},
locale: Locale,
parameters: NavigationParams,
router: Object,
|};
type InternalProps = {|
...Props,
dispatch: Function,
|};
/**
* Displays a list of entities and their current translation.
*
* This component will fetch entities when loaded, then display those
* entities. It interacts with `core/navigation` when an entity is selected.
*
*/
export class EntitiesListBase extends React.Component<InternalProps> {
componentDidUpdate(prevProps: InternalProps) {
// Whenever the route changes, we want to verify that the user didn't
// change locale, project or resource. If they did, then we'll have
// to reset the current list of entities, in order to start a fresh
// list and hide the previous entities.
//
// Notes:
// * It might seem to be an anti-pattern to change the state after the
// component has rendered, but that's actually the easiest way to
// implement that feature. Note that the first render is not shown
// to the user, so there should be no blinking here.
// Cf. https://reactjs.org/docs/react-component.html#componentdidupdate
// * Other solutions might involve using `connected-react-router`'s
// redux actions (see https://stackoverflow.com/a/37911318/1462501)
// or using `history.listen` to trigger an action on each location
// change.
// * I haven't been able to figure out how to test this feature. It
// is possible that going for another possible solutions will make
// testing easier, which would be very desirable.
const previous = prevProps.parameters;
const current = this.props.parameters;
if (
previous.locale !== current.locale ||
previous.project !== current.project ||
previous.resource !== current.resource ||
previous.search !== current.search ||
previous.status !== current.status
) {
this.props.dispatch(actions.reset());
}
}
selectEntity = (entity: DbEntity) => {
this.props.dispatch(
navigation.actions.updateEntity(this.props.router, entity.pk.toString())
);
}
getMoreEntities = () => {
const { entities, parameters } = this.props;
const { locale, project, resource, search, status } = parameters;
// Temporary fix for the infinite number of requests from InfiniteScroller
// More info at:
// * https://github.com/CassetteRocks/react-infinite-scroller/issues/149
// * https://github.com/CassetteRocks/react-infinite-scroller/issues/163
if (entities.fetching) {
return;
}
// Currently shown entities should be excluded from the next results.
const currentEntityIds = entities.entities.map(entity => entity.pk);
this.props.dispatch(
actions.get(
locale,
project,
resource,
currentEntityIds,
search,
status,
)
);
}
render() {
const state = this.props;
const selectedEntity = state.parameters.entity;
// InfiniteScroll will display information about loading during the request
const hasMore = state.entities.fetching || state.entities.hasMore;
return <div className="entities">
<InfiniteScroll
pageStart={ 1 }
loadMore={ this.getMoreEntities }
hasMore={ hasMore }
loader={ <CircleLoader key={0} /> }
useWindow={ false }
threshold={ 600 }
>
{ (hasMore || state.entities.entities.length) ?
<ul>
{ state.entities.entities.map((entity, i) => {
return <Entity
entity={ entity }
locale={ state.locale }
selectEntity={ this.selectEntity }
key={ i }
selected={ entity.pk === selectedEntity }
/>;
}) }
</ul>
:
// When there are no results for the current search.
<h3 className="no-results">
<div className="fa fa-exclamation-circle"></div>
No results
</h3>
}
</InfiniteScroll>
</div>;
}
}
const mapStateToProps = (state: Object): Props => {
return {
entities: state[NAME],
parameters: navigation.selectors.getNavigationParams(state),
locale: locales.selectors.getCurrentLocaleData(state),
router: state.router,
};
};
export default connect(mapStateToProps)(EntitiesListBase);