Created
February 21, 2018 16:40
-
-
Save Craga89/acaf15f25122b9ed4bf29ad7e90ed722 to your computer and use it in GitHub Desktop.
Generic withGridFunctionality HOC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PropTypes from 'prop-types'; | |
/** | |
* Generates a map of query parameters and their associated values when given | |
* a `queryProps` map and some `props` values. | |
* | |
* @param queryProps {Object} Map of prop names to associated queryProp definitions | |
* @param props {Object} Current props to generate values from | |
* @returns {Object} Query parameters map | |
*/ | |
const generateQueryValues = (queryProps, props) => | |
Object.keys(queryProps).reduce((memo, propName) => { | |
const { param, defaultValue } = queryProps[propName]; | |
const value = props[propName]; | |
if (value !== defaultValue && value !== null) { | |
memo[param] = value; | |
} | |
return memo; | |
}, {}); | |
/** | |
* Generates a new component with common Grid functionality, such as fetch and toastr handling. | |
* | |
* @method withGridFunctionality | |
* @param options {Object} Options object | |
* @param options.url {String} URL the Grid collection is addressable by | |
* @param [options.resetProps=[]] {Array<String>} Array of prop names that when changed, cause `fetchReset` to be called | |
* @param [options.queryProps={}] {Object} Map of props to reflect in the URL as query parameters | |
* @returns {Object} Higher Order Component | |
*/ | |
export default ({ | |
url, | |
resetProps = [], | |
queryProps = {} | |
}) => compose( | |
// Used by `withHandlers` below to show toaster popups | |
getContext({ | |
showToaster: PropTypes.func | |
}), | |
injectData({ | |
url, | |
dataProp: 'items', | |
requestProp: 'requestItems', | |
loadingProp: 'isItemsLoading', | |
resetProp: 'resetItems', | |
initialData: [] | |
}), | |
// Introduces new `fetchReset` and `fetchNext` props, which can be used by lower components | |
// to programatically reset the grid to initial results, or fetch the next page when needed | |
withHandlers(() => { | |
let index = 0; | |
let pageSize = 30; | |
let isFetching = false; | |
// Stop React Virtualized from trying to fetch the next set of records if number of results is within the threshold. | |
let lastFetchWithinThreshold = false; | |
const fetch = ({ data, requestData }) => { | |
isFetching = true; | |
return requestData('get', { | |
data | |
}) | |
.then((data) => { | |
isFetching = false; | |
lastFetchWithinThreshold = data.items.length <= 5; | |
return data; | |
}) | |
.catch((e) => { | |
isFetching = false; | |
lastFetchWithinThreshold = false; | |
throw e; | |
}); | |
}; | |
return { | |
fetchReset: (props) => debounce(async() => { | |
index = 0; | |
pageSize = 30; | |
await fetch(props); | |
}, 300), | |
fetchNext: (props) => debounce(async(force = false) => { | |
const shouldBypass = !force && (isFetching || lastFetchWithinThreshold); | |
lastFetchWithinThreshold = false; | |
if (shouldBypass) { | |
return; | |
} | |
// Move to the next page index | |
index += pageSize; | |
// Fetch the new data | |
const data = await fetch(props); | |
// Account for scenario where we are given less items than we asked for | |
index -= pageSize - data.items.length; | |
// Merge the items into the existing items array, de-duping | |
props.setItems(mergeData(props.items, data.items)); | |
// No more records? Show the toaster | |
if (data && data.items.length < 1) { | |
props.showToaster({ | |
text: 'No more records are currently available' | |
}); | |
} | |
}, | |
300) | |
}; | |
}), | |
// Handles `fetchReset` calls when resetProps change, and updates query parameters | |
// defined in the `queryProps` to reflect internal prop state when props update. | |
lifecycle({ | |
componentDidMount() { | |
this.props.fetchReset(); | |
}, | |
componentDidUpdate(prevProps) { | |
// Do we need to reset? | |
Object.keys(resetProps).forEach((propName) => { | |
if (this.props[propName] !== prevProps[propName]) { | |
this.props.fetchReset(); | |
} | |
}); | |
// Do we need to update the query string? | |
const shouldQueryUpdate = Object.keys(queryProps).some((key) => | |
this.props[key] !== prevProps[key] | |
); | |
if (shouldQueryUpdate) { | |
const queryValues = generateQueryValues(queryProps, this.props); | |
history.pushState(queryValues, null, '?' + querystring.stringify(queryValues)); | |
} | |
} | |
}), | |
// Passes down the exposed `list.scrollToRow` functionality as a prop | |
// to the rest of the Grid so it's easy to programatically scroll later on | |
lifecycle({ | |
componentDidMount() { | |
this.setState({ | |
onListReady: (list) => { | |
this._list = list; | |
const { onListReady } = this.props; | |
if (onListReady) { | |
onListReady(list); | |
} | |
}, | |
scrollToRow: (index) => | |
this._list.scrollToRow(index) | |
}); | |
}, | |
componentWillUnmount() { | |
this._list = null; | |
} | |
}), | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment