Skip to content

Commit aa01e3d

Browse files
committed
PM-902 - load more projects on dropdown scroll
1 parent f9e5ac1 commit aa01e3d

File tree

6 files changed

+66
-13
lines changed

6 files changed

+66
-13
lines changed

src/actions/sidebar.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
6767
}
6868
}
6969

70+
// Load next page of projects
71+
export function loadNextProjects () {
72+
return (dispatch, getState) => {
73+
const { projectFilters, projectsPage } = getState().sidebar
74+
75+
loadProjects('', _.assign({}, projectFilters, {
76+
perPage: PROJECTS_PAGE_SIZE,
77+
page: projectsPage + 1
78+
}))(dispatch, getState)
79+
}
80+
}
81+
7082
/**
7183
* Unloads projects of the authenticated user
7284
*/

src/components/ChallengesComponent/ChallengeList/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,8 @@ class ChallengeList extends Component {
401401
isBillingAccountLoading,
402402
selfService,
403403
challengeTypes,
404-
loginUserRoleInProject
404+
loginUserRoleInProject,
405+
fetchNextProjects
405406
} = this.props
406407
const isReadOnly = checkReadOnlyRoles(this.props.auth.token) || loginUserRoleInProject === PROJECT_ROLES.READ
407408
const isAdmin = checkAdmin(this.props.auth.token)
@@ -506,6 +507,9 @@ class ChallengeList extends Component {
506507
<Select
507508
name='project'
508509
options={projectOptions}
510+
cacheOptions
511+
captureMenuScroll
512+
onMenuScrollBottom={fetchNextProjects}
509513
placeholder='All Projects'
510514
value={projectOption}
511515
onChange={e =>
@@ -853,6 +857,7 @@ ChallengeList.defaultProps = {
853857

854858
ChallengeList.propTypes = {
855859
challenges: PropTypes.arrayOf(PropTypes.object),
860+
fetchNextProjects: PropTypes.func.isRequired,
856861
projects: PropTypes.arrayOf(PropTypes.object),
857862
activeProject: PropTypes.shape({
858863
id: PropTypes.number,

src/components/ChallengesComponent/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ const ChallengesComponent = ({
4646
isBillingAccountLoading,
4747
selfService,
4848
auth,
49-
challengeTypes
49+
challengeTypes,
50+
fetchNextProjects
5051
}) => {
5152
const [loginUserRoleInProject, setLoginUserRoleInProject] = useState('')
5253
const isReadOnly = checkReadOnlyRoles(auth.token) || loginUserRoleInProject === PROJECT_ROLES.READ
@@ -118,6 +119,7 @@ const ChallengesComponent = ({
118119
<div className={styles.challenges}>
119120
<ChallengeList
120121
challenges={challenges}
122+
fetchNextProjects={fetchNextProjects}
121123
projects={projects}
122124
warnMessage={warnMessage}
123125
isLoading={isLoading}
@@ -160,6 +162,7 @@ const ChallengesComponent = ({
160162
ChallengesComponent.propTypes = {
161163
challenges: PropTypes.arrayOf(PropTypes.object),
162164
projects: PropTypes.arrayOf(PropTypes.object),
165+
fetchNextProjects: PropTypes.func.isRequired,
163166
activeProject: PropTypes.shape({
164167
id: PropTypes.number,
165168
name: PropTypes.string

src/components/Select/index.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
1-
import React from 'react'
1+
import React, { useEffect, useState } from 'react'
22
import _ from 'lodash'
3-
import ReactSelect from 'react-select'
3+
import ReactSelect, { components } from 'react-select'
44
import CreatableSelect from 'react-select/creatable'
55
import AsyncSelect from 'react-select/async'
66
import PT from 'prop-types'
77
import styles from './styles'
88

9+
const menuList = ({ onMenuScrollBottom }) => {
10+
let menuListRef = null
11+
12+
const handleOnScroll = (ev) => {
13+
ev.preventDefault()
14+
const el = ev.target
15+
if (el.scrollTop + el.offsetHeight >= el.scrollHeight - 10) {
16+
onMenuScrollBottom()
17+
}
18+
}
19+
20+
const setMenuListRef = (ref) => {
21+
if (!menuListRef) {
22+
ref.addEventListener('scroll', handleOnScroll, false)
23+
}
24+
menuListRef = ref
25+
}
26+
return (props) => (
27+
<components.MenuList key='projects-select--menu-list' {...props} innerRef={onMenuScrollBottom ? (ref) => { setMenuListRef(ref); props.innerRef(ref) } : props.innerRef} />
28+
)
29+
}
30+
931
export default function Select (props) {
1032
const { selectRef, isCreatable, isAsync } = props
33+
const [components, setComponents] = useState({})
34+
35+
useEffect(() => {
36+
setComponents((prev) => ({ ...prev, MenuList: menuList(props) }))
37+
}, [props.onMenuScrollBottom])
1138

1239
if (isAsync) {
1340
return (<AsyncSelect
@@ -32,6 +59,7 @@ export default function Select (props) {
3259
{...props}
3360
autosize={false}
3461
styles={styles}
62+
components={components}
3563
/>
3664
)
3765
}
@@ -45,5 +73,6 @@ Select.defaultProps = {
4573
Select.propTypes = {
4674
selectRef: PT.func,
4775
isCreatable: PT.bool,
48-
isAsync: PT.bool
76+
isAsync: PT.bool,
77+
onMenuScrollBottom: PT.func
4978
}

src/containers/Challenges/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '../../actions/challenges'
1717
import { loadProject, updateProject } from '../../actions/projects'
1818
import {
19-
loadProjects,
19+
loadNextProjects,
2020
setActiveProject,
2121
resetSidebarActiveParams
2222
} from '../../actions/sidebar'
@@ -136,7 +136,8 @@ class Challenges extends Component {
136136
dashboard,
137137
selfService,
138138
auth,
139-
metadata
139+
metadata,
140+
fetchNextProjects
140141
} = this.props
141142
const { challengeTypes = [] } = metadata
142143
return (
@@ -148,6 +149,7 @@ class Challenges extends Component {
148149
? reduxProjectInfo
149150
: {})
150151
}}
152+
fetchNextProjects={fetchNextProjects}
151153
warnMessage={warnMessage}
152154
setActiveProject={setActiveProject}
153155
dashboard={dashboard}
@@ -229,6 +231,7 @@ Challenges.propTypes = {
229231
dashboard: PropTypes.bool,
230232
auth: PropTypes.object.isRequired,
231233
loadChallengeTypes: PropTypes.func,
234+
fetchNextProjects: PropTypes.func.isRequired,
232235
metadata: PropTypes.shape({
233236
challengeTypes: PropTypes.array
234237
})
@@ -257,7 +260,7 @@ const mapDispatchToProps = {
257260
loadChallengesByPage,
258261
resetSidebarActiveParams,
259262
loadProject,
260-
loadProjects,
263+
fetchNextProjects: loadNextProjects,
261264
updateProject,
262265
loadChallengeTypes,
263266
setActiveProject,

src/reducers/sidebar.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { toastFailure } from '../util/toaster'
1515
const initialState = {
1616
activeProjectId: -1,
1717
isLoading: false,
18+
projectFilters: {},
1819
projects: [],
19-
total: 0,
20-
page: 0,
20+
projectsTotal: 0,
21+
projectsPage: 0,
2122
isLoadProjectsSuccess: false
2223
}
2324

@@ -29,14 +30,14 @@ export default function (state = initialState, action) {
2930
return {
3031
...state,
3132
projects: action.projects,
32-
total: action.total,
33-
page: action.page,
33+
projectsTotal: action.total,
34+
projectsPage: action.page,
3435
isLoading: false,
3536
isLoggedIn: true,
3637
isLoadProjectsSuccess: true
3738
}
3839
case UNLOAD_PROJECTS_SUCCESS:
39-
return { ...state, total: 0, page: 0, projects: [], isLoading: false, isLoggedIn: true, isLoadProjectsSuccess: false }
40+
return { ...state, projectsTotal: 0, projectsPage: 0, projects: [], isLoading: false, isLoggedIn: true, isLoadProjectsSuccess: false }
4041
case LOAD_PROJECTS_PENDING:
4142
return { ...state, isLoading: true }
4243
case LOAD_PROJECTS_FAILURE: {

0 commit comments

Comments
 (0)