UNPKG

redux-code

Version:

Redux helpers for actions and reducers

176 lines (175 loc) 6.57 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ export const entitiesActions = { /** * Creates an action to add a new entity to the store. * @param {entity} entity The entity to add. */ addOne: (entity) => entity, /** * Creates an action to add multiple entities to the store. * @param {entities} entities The entities to add. */ addMany: (entities) => entities, /** * Creates an action to update an entity in the store. * @param {entity} entity The entity to update. */ updateOne: (entity) => entity, /** * Creates an action to update multiple entities in the store. * @param {entities} entities The entities to update. */ updateMany: (entities) => entities, /** * Creates an action to insert or update an entity in the store. * @param {entity} entity The entity to insert or update. */ upsertOne: (entity) => entity, /** * Create an action to insert or update multiple entities in the store. * @param {entities} entities The entities to insert or update. */ upsertMany: (entities) => entities, /** * Creates an action to replace an entity in the store. * @param {entity} entity The entity to replace. */ setOne: (entity) => entity, /** * Creates an action to replace multiple entities in the store. * @param {entities} entities The entities to replace. */ setMany: (entities) => entities, /** * Creates an action to replace all entities in the store. * @param {entities} entities The entities to replace. */ setAll: (entities) => entities, /** * Creates an action to remove an entity from the store. * @param {entity} entity The entity to remove. */ removeOne: (entity) => entity, /** * Creates an action to remove multiple entities from the store. * @param {entities} entities The entities to remove. */ removeMany: (entities) => entities, /** * Creates an action to remove all entities from the store. */ removeAll: () => undefined, } /** * Create a mixin for a reducer that adds the ability to handle entities. * @param {actions} actions The actions to handle. * @param {config} config The configuration. */ export const entitiesReducer = ( actions, { processEntity = (entity) => entity, selectId = (entity) => entity.id, sortComparer, updateComparer = (a, b) => a === b, } = {}, ) => { function merge(id, entity, entities) { const source = entities[id] if (updateComparer(source, entity)) return entities return Object.assign(Object.assign({}, entities), { [id]: Object.assign(Object.assign({}, source), entity), }) } function sorter(ids, entities) { if (!sortComparer) return ids return Object.values(entities).sort(sortComparer).map(selectId) } function addMany(items, state) { const entities = Object.fromEntries([ ...Object.entries(state.entities), ...items.map((entity) => [selectId(entity), processEntity(entity)]), ]) return Object.assign(Object.assign({}, state), { entities, ids: sorter([...state.ids, ...items.map(selectId)], entities), }) } function updateMany(updates, state) { let entities = state.entities for (let entity of updates) { const id = selectId(entity) if (!(id in entities)) continue const source = entities[id] entity = processEntity(entity) if (!updateComparer(entity, source)) entities = Object.assign(Object.assign({}, entities), { [id]: Object.assign(Object.assign({}, source), entity), }) } if (state.entities === entities) return state return Object.assign(Object.assign({}, state), { entities, ids: sorter(state.ids, entities) }) } function setMany(items, state, strategy = replace) { let ids = state.ids let entities = state.entities for (const entity of items) { const id = selectId(entity) if (id in entities) entities = strategy(id, processEntity(entity), entities) else { ids = [...ids, id] entities = Object.assign(Object.assign({}, entities), { [id]: entity }) } } return Object.assign(Object.assign({}, state), { ids: sorter(ids, entities), entities }) } function removeMany(items, state) { const ids = items.map(selectId) return Object.assign(Object.assign({}, state), { ids: state.ids.filter((id) => !ids.includes(id)), entities: Object.fromEntries( // eslint-disable-next-line @typescript-eslint/no-unused-vars Object.entries(state.entities).filter(([id, _]) => !ids.includes(id)), ), }) } return { [actions.addOne.type]: (state, { payload }) => addMany([payload], state), [actions.addMany.type]: (state, { payload }) => addMany(payload, state), [actions.removeOne.type]: (state, { payload }) => removeMany([payload], state), [actions.removeMany.type]: (state, { payload }) => removeMany(payload, state), [actions.removeAll.type]: (state) => Object.assign(Object.assign({}, state), { ids: [], entities: {} }), [actions.updateOne.type]: (state, { payload }) => updateMany([payload], state), [actions.updateMany.type]: (state, { payload }) => updateMany(payload, state), [actions.upsertOne.type]: (state, { payload }) => setMany([payload], state, merge), [actions.upsertMany.type]: (state, { payload }) => setMany(payload, state, merge), [actions.setOne.type]: (state, { payload }) => setMany([payload], state), [actions.setMany.type]: (state, { payload }) => setMany(payload, state), [actions.setAll.type]: (state, { payload }) => { const items = payload.map((item) => { const id = selectId(item) return updateComparer(item, state.entities[id]) ? state.entities[id] : item }) return addMany(items, Object.assign(Object.assign({}, state), { ids: [], entities: {} })) }, } } /** * Select the entities from the state. * @param {state} state The state. */ export const selectEntities = (state) => state.ids.map((id) => state.entities[id]) /** * Select the entity with the given id from the state. * @param {state} state The state. */ export const selectEntityById = (state, id) => state.entities[id] /** * Select the total number of entities in the state. * @param {state} state The state. */ export const selectEntitiesTotal = (state) => state.ids.length // const merge = (id, entity, entities) => ({ ...entities, [id]: { ...entities[id], ...entity } }) const replace = (id, entity, entities) => Object.assign(Object.assign({}, entities), { [id]: entity })