YAEM: another react/redux entity manager
There are tens of helper libraries for redux to work with entities in the state. And they are there for a good reason: if you what to handle them manually you have to face hard to read code containing too many spread operators, slices, arrays map and checks. Delete and update operations can be tricky while keeping state immutability. Moreover manage entities using arrays can be very inefficient for React rendering.
I searched for an entity management library while I was developing one of my last projects in xtream. This project required crud operations on different entities. What I found is a lot of opinioned libraries which create reducers, actions, etc…., without having the possibility of customizing them. Coming from the dark side (Angular), I appreciated a lot @ngrx/entity. I had no luck in finding something as similar and simple as ngrx one (there are some libraries that require custom classes and specific models creation but I wanted to use common typescript interfaces).
Next step was looking into the ngrx implementation of the entity library, checking if it could be used. It turned out that it depends from ngrx/store, but only for the getSelectors
functionality. So, why not creating a porting? This is how YAEM (Yet Another Entity Manager) was born.
YAEM installation
You can install it using npm via:
npm i @xtream/yaem
It is written in typescript . Therefore, types definitions are included.
Features
The library includes two different features: the entity adapter and the memoized selectors creation (sorry reselect), took from @ngrx/store .
Entity adapter
The entity adapter is a simple helper object. It exposes some methods to help write a better reducer (clean and easy to read) and a better state organisation to improve performances. It manages entities in the following state structure. The reason behind this approach will be covered later on in the article.
We will consider an admin panel that has to execute CRUD operations on users to showcase a standard usage of the entity adapter.
Let’s have a look at the feature of YAEM shown here:
First of all, we define the state extending the generic interface EntityState, adding any other property that you need (such additional properties will not be modified by the entity adapter).
Now, we create the adapter instance. The factory method accepts two optional parameters: selectId, useful to map an entity to its id that defaults to entity => entity.id
and a sortComparer function to keep the list of entities in a specific order at each operation on the state.
After that it’s time for the default state: when calling getDefaultState, the adapter returns a default entity state. If you defined other properties you have to pass their default values that will be merged into the returned object.
Now the interesting part: the reducer. Given the state, and given that you need to handle simple operations like CRUDs, how would you normally write a reducer? a hell of spread and checks! The adapter will do everything for you, returning the same state if the operation will have no effects or creating a new state applying the requested modification.
The method getSelectors returns a set of functions that accept the entities state and map to the list of ids, the dictionary of entities or a list of entities etc.
Here is the complete list of the adapter API:
addOne
: Add one entity to the collection
addMany
: Add multiple entities to the collection
addAll
: Replace current collection with provided collection
removeOne
: Remove one entity from the collection
removeMany
: Remove multiple entities from the collection, by id or by predicate
removeAll
: Clear entity collection
updateOne
: Update one entity in the collection
updateMany
: Update multiple entities in the collection
upsertOne
: Add or Update one entity in the collection
upsertMany
: Add or Update multiple entities in the collection
map
: Update multiple entities in the collection by defining a map function, similar to Array.map
Why this state shape
Of course, you might be wondering: “Why are we using this state shape?” Imagine you want to render a list of editable cards representing your entity, and you decide to organize your state in a list of entites. To keep immutability every time that an item changes the entire list must be recreated and the react component will rerender because the list reference changed. On the contrary if the component rendering the list is binded to the list of ids, you enable a finer re-rendering in case of changes to your entities: for each id, a component that independently takes the specific object from the state is rendered.
Memoized selectors
Instead of completely removing everything that was linked to the ngrx/store, I kept the selector memoization functionality inside YAEM, because I did not want to remove the getSelectors API and the implementation is lean. The selector memoization is composed by two APIs: createFeatureSelector and createSelector.
createFeatureSelector returns a function that selects the state under the specified key in the root state, so you should have combinerReducer in your root:
It is just an elegant way to avoid dependence of the user module from the entire App State, selecting the users state part by key.
The create selector function is almost the same as reselect, the selectors are composable to dive into the state (not too much! stay flat!)
If you have ever used reselect you can see that the API are identical. The result of createFeatureSelector is generally used as root selector retrieving, in this case, the users state. Every selector is memoized so your react component will not rerender unless necessary (if we do a proper job as developers).