YAEM: another react/redux entity manager

Luca Micieli
4 min readSep 23, 2019

--

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).

Thank you reader for reaching here. Do you fell that this porting will just increase the entropy of redux-related libraries or it could be useful and you will give it a shot? Please feel free to leave your opinion here or write me on twitter or linkedin.

--

--

Luca Micieli

Front-end developer and Co-Founder @xtream. Angular, React, and TypeScript lover.