Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@MarkMurphy
Last active May 24, 2016 13:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MarkMurphy/9b964b3cfa7b30736734e6a908b3a6b4 to your computer and use it in GitHub Desktop.
Save MarkMurphy/9b964b3cfa7b30736734e6a908b3a6b4 to your computer and use it in GitHub Desktop.
redux-crud pagination
// containers/AlbumPage.js
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { browserHistory } from 'react-router'
import { fetchAlbum, deleteAlbum } from '../actions/albums'
import { listPhotos } from '../actions/photos'
import bindAll from 'lodash/bindAll'
import AlbumHeader from '../components/AlbumHeader'
import PhotoGallery from '../components/PhotoGallery'
function loadData(props) {
const { id } = props
props.fetchAlbum(id)
props.listPhotos({ album: id })
}
class AlbumPage extends Component {
constructor(props) {
super(props)
bindAll(this, [
'handleAlbumDelete'
])
}
componentWillMount() {
loadData(this.props)
}
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
loadData(nextProps)
}
}
handleAlbumDelete(album) {
this.props.deleteAlbum(album.id)
browserHistory.push(`/albums`)
}
render() {
const { album, albumPhotos } = this.props
if (!album) {
return <div>Loading album...</div>
}
return (
<div>
<AlbumHeader album={album} onAlbumDelete={this.handleAlbumDelete} />
<PhotoGallery photos={albumPhotos} />
</div>
)
}
}
function mapStateToProps(state, props) {
const {
albums,
photos,
pagination: { photosByAlbum },
} = state
const id = props.params.id
const albumPhotosPagination = photosByAlbum[id] || { ids: [] }
const album = albums.find(album => album.id == id)
// Use this line (line 69) instead of the one above (line 67) if you modify redux-crud to use Maps instead of Arrays.
// const album = albums.get(id)
const albumPhotos = photos.filter(item => albumPhotosPagination.ids.includes(item.id))
// Use this line (line 73) instead of the one above (line 71) if you modify redux-crud to use Maps instead of Arrays.
// const albumPhotos = albumPhotosPagination.ids.map(id => photos.get(id))
return {
id,
album,
albumPhotos
}
}
const mapDispatchToProps = {
fetchAlbum,
deleteAlbum,
listPhotos
}
export default connect(mapStateToProps, mapDispatchToProps)(AlbumPage)
// reducers/pagination.js
import merge from 'lodash/merge'
import union from 'lodash/union'
import { combineReducers } from 'redux'
import { actionTypesFor } from 'redux-crud'
// Creates a reducer managing pagination, given the action types to handle,
// and a function telling how to extract the key from an action.
export function paginate({ types, mapActionToKey }) {
if (!Array.isArray(types) || types.length !== 3) {
throw new Error('Expected types to be an array of three elements.')
}
if (!types.every(t => typeof t === 'string')) {
throw new Error('Expected types to be strings.')
}
if (typeof mapActionToKey !== 'function') {
throw new Error('Expected mapActionToKey to be a function.')
}
const [ requestType, successType, failureType ] = types
const initialState = {
isFetching: false,
// nextPageUrl: undefined,
// pageCount: 0,
ids: []
}
function updatePagination(state = initialState, action) {
switch (action.type) {
case requestType:
return merge({}, state, {
isFetching: true
})
case successType:
return merge({}, state, {
isFetching: false,
ids: union(state.ids, action.records.map(record => record.id)),
// nextPageUrl: action.response.nextPageUrl,
// pageCount: state.pageCount + 1
})
case failureType:
return merge({}, state, {
isFetching: false
})
default:
return state
}
}
return function updatePaginationByKey(state = {}, action) {
switch (action.type) {
case requestType:
case successType:
case failureType:
const key = mapActionToKey(action)
// If key is exactly `false`, skip this update.
if (typeof key === false) {
return state
}
if (typeof key !== 'string') {
throw new Error('Expected key to be a string.')
}
return merge({}, state, {
[key]: updatePagination(state[key], action)
})
default:
return state
}
}
}
const albumActionTypes = actionTypesFor('albums')
const photoActionTypes = actionTypesFor('photos')
// Updates the pagination data for different actions.
export default pagination = combineReducers({
albumsByUser: paginate({
mapActionToKey: action => action.data.user || false,
types: [
albumActionTypes.fetchStart,
albumActionTypes.fetchSuccess,
albumActionTypes.fetchError
]
}),
photosByAlbum: paginate({
mapActionToKey: action => action.data.album || false,
types: [
photoActionTypes.fetchStart,
photoActionTypes.fetchSuccess,
photoActionTypes.fetchError
]
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment