Let's say I have a hook that returns an entity's state, as well as extends it with some methods for enacting mutations:
// Loads an entity from the store and provides methods to update and remove it
const useEntity = (entityId) => {
// Selects the entity from the store
const entity = useSelector(selectEntity(entityId));
// Updates the entity in the store
const update = (value) => {
// ...logic
};
// Removes the entity from the store
const remove = () => {
// ...logic
};
return {
entity, // The entity from the store
update, // The update method
remove // The remove method
};
};
I would like to create a "wrapper" hook that "extends" all entities in the store using my useEntity
hook
// Extends EVERY entity in the store with the useEntity hook defined previously
const useEntities = () => {
// Selects all entities from the store
const allEntities = useSelector(selectAllEntities);
const extendedEntities = allEntities.map((entity) => useEntity(entity.id));
return extendedEntities;
}
Obviously, the logic in useEntities
violates the Rules of Hooks as the hooks get called inside a loop.
What then is the correct way to extend my entities to have the additional custom methods?
Ideally, I'd like to be able to do something like
const extendedEntities = useEntities()
extendedEntities[0].update("New value") // Updates the value for entity 0
extendedEntieis[6].remove() // Removes entity 6
extendedEntities[3].entity // returns the value of entity 3
// ...etc.
Instead of relying so heavily on the useSelector
hook you could modify the useEntity
hook to be a augmentEntity
utility that is either passed or closes over a reference to the store
object where it can internally select the state it requires.
In other words, recall that selectEntity(entityId)
is a selector function that is passed the current Redux state and returns a computed value. Convert useSelector(selectEntity(entityId))
to selectEntity(entityId)(state)
.
Something like the following:
/** Loads an entity from the store and provides methods to update and remove it */
const augmentEntity = (entityId) => {
const state = store.getState();
// Selects the entity from the store
const entity = selectEntity(entityId)(state);
// Updates the entity in the store
const update = (value) => {
// ...logic
};
// Removes the entity from the store
const remove = () => {
// ...logic
};
return {
entity, // The entity from the store
update, // The update method
remove // The remove method
};
};
const useEntities = () => {
// Selects all entities from the store
const allEntities = useSelector(selectAllEntities);
// Augments all entities with update/remove functions
return allEntities.map((entity) => augmentEntity(entity.id));
};
Since selectAllEntities
already returns all the entities there is probably no reason to again select specific entities directly, just pass the mapped entities instead of their ids to the utility and augment as needed.
Example:
const augmentEntity = (entity) => {
// Updates the entity in the store
const update = (value) => {
// ...logic
};
// Removes the entity from the store
const remove = () => {
// ...logic
};
return {
entity, // The entity from the store
update, // The update method
remove // The remove method
};
};
const useEntities = () => {
// Selects all entities from the store
const allEntities = useSelector(selectAllEntities);
// Augments all entities with update/remove functions
return allEntities.map(augmentEntity);
};