/* eslint-disable import/prefer-default-export */
import { createSelector } from 'reselect';
import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import { PAGINATION_NONE } from '../data/paginationType';
import createShallowEqualSelector from '../util/reducers/createShallowEqualSelector';
/**
* Contains the function
* {@link module:collectionSelector~makeCollectionSelector|makeCollectionSelector}
* that can be used to select all the entities in a collection from redux state.
* @module collectionSelector
* @category redux-state
*/
/**
* Result returned by a `collectionSelector`
* @typedef CollectionSelectorResult
* @property entities {Object[]} An array of entity data, if found in the `entitiesSelector`
* @property entityRefs {Object[]} An array of entities in the collection
* @property entityRefs[].id {string} The id of the entity
* @property entityRefs[].type {string} The type of the entity
* @property entityRefs[].data {*} The entity data, if found in the `entitiesSelector`. Note that
* this is the same object as referenced in the `entities` array, but it is also provided here
* for convenience
* @property pagination {object} Object containing data pagination info
* @property pagination.type {string} One of the pagination types defined in the
* {@link module:paginationType|paginationType} module
* @property pagination.total {number} The total number of entities available on the server. **This
* may be smaller than the amount of entities currently loaded!**
* @property pagination.offset {number} _(offset type only)_ The offset of the first
* entity of the `entities` array.
* @property pagination.atBegin {boolean} _(partition type only)_ `true` if we know that there are
* no more entities on the server that come before the `entities` array
* @property pagination.atEnd {boolean} _(partition type only)_ `true` if we know that there are
* no more entities on the server that come after the `entities` array
*/
/**
* Selector returned by
* {@link module:collectionSelector~makeCollectionSelector|makeCollectionSelector}
* @callback collectionSelector
* @param state {Object} The current redux state
* @param props {Object}
* @param props.collectionId {string} The id of the collection to get. Should be one of the ids
* defined in {@link module:collectionIds|collectionIds}
* @returns {CollectionSelectorResult} An object containing the selector data. See
* {@link module:collectionSelector~CollectionSelectorResult|CollectionSelectorResult}
*/
/**
* Create a const empty result to make sure the reference doesn't change every time
* @type {Array}
*/
const EMPTY_ARRAY = [];
/**
* Empty result to return when collection is not defined
* @private
*/
const defaultCollectionSelectorResult = {
entities: EMPTY_ARRAY,
entityRefs: EMPTY_ARRAY,
pagination: {
type: PAGINATION_NONE,
},
};
const makeCollectionTypesSelector = () =>
createSelector(
(state, { collectionId }) => state.collections[collectionId],
collection => (collection ? uniq(collection.refs.map(ref => ref.type)) : EMPTY_ARRAY),
);
const makeEntityStateByTypeSelector = () =>
createSelector(
state => state.entities,
makeCollectionTypesSelector(),
(entities, collectionTypes) => pick(entities, collectionTypes),
);
const makeCollectionDataSelector = () =>
createShallowEqualSelector(
makeEntityStateByTypeSelector(),
(state, { collectionId }) =>
state.collections[collectionId] && state.collections[collectionId].refs,
(entitiesByType, collectionRefs) => {
if (!collectionRefs || !collectionRefs.length) {
return EMPTY_ARRAY;
}
return collectionRefs.map(ref =>
ref ? (entitiesByType[ref.type] && entitiesByType[ref.type][ref.id]) || null : null,
);
},
);
const makeCollectionDataEnhancerSelector = () =>
createShallowEqualSelector(
makeCollectionDataSelector(),
(state, { collectionId }) =>
state.collections[collectionId] && state.collections[collectionId].refs,
(entities, collectionRefs) => {
if (!collectionRefs || !collectionRefs.length) {
return {
entities: EMPTY_ARRAY,
entityRefs: EMPTY_ARRAY,
};
}
return {
entities,
entityRefs: collectionRefs.map((ref, index) =>
ref
? {
data: entities[index],
...ref,
}
: ref,
),
};
},
);
/**
* Returns a new {@link https://github.com/reactjs/reselect|reselect} selector that can be used
* to get all the entities referenced in a collection.
* @function makeCollectionSelector
* @returns {collectionSelector} The new selector. See
* {@link module:collectionSelector~collectionSelector|collectionSelector}
* @example const connector = connect(
* () => {
* const collectionSelector = makeCollectionSelector();
*
* return (state, { discussionId }) => {
* const comments = collectionSelector(state, {
* collectionId: communityCommentsCollectionId(discussionId, false),
* });
*
* return {
* comments: comments.entities,
* hasMore: (comments.pagination.total !== null) && (
* comments.pagination.total > comments.entities.length
* ),
* };
* };
* }
* );
*/
export const makeCollectionSelector = () =>
createSelector(
makeCollectionDataEnhancerSelector(),
(state, { collectionId }) => state.collections[collectionId],
({ entities, entityRefs }, collection) => {
if (!collection) {
return defaultCollectionSelectorResult;
}
const { pagination } = collection;
return { entities, entityRefs, pagination };
},
);
/**
* Instance of collectionSelector that can be used when memoization is not
* relevant. This function should **NOT** be used in the `connect()` of a component.
* Use {@link module:collectionSelector~makeCollectionSelector|makeCollectionSelector} to create a
* selector instead.
*
* @function getEntitiesFromCollection
*/
export const getEntitiesFromCollection = makeCollectionSelector();