Hello there. If you are following this series, you know I sent this proposal to the Zustand team. After some weeks, their feedback was to wait, stating that this was in the field of third-party libraries. Considering this, and since I didn't find anything similar, I created zustand-entity-adapter.
What is zustand-entity-adapter?
It's a tiny (~3kb/~1kb gzipped) library to create an Entity Adapter for Zustand. It also allows you to create a convenient store that includes any additional structure and the actions and selectors to be used to manage the entities.
Moreover, this library takes advantage of how simple Zustand is and integrates with it very well.
How to use zustand-entity-adapter?
Some of the API for the library has been developed in this series and can be used as documentation. Additionally, you can find more information on Entity Adapters from @ngrx
and RTK
.
As you may notice, this API draws inspiration from both the @ngrx/entity
package and RTK's createEntityAdapter
. But since Zustand is not using a reducer, there are some differences between those implementations and this one.
createEntityAdapter
When using createEntityAdapter
, this will return an object with methods getState
, getSelectors
, and getActions
. They all have similar counterparts in both implementations, but they have subtle differences because of Zustand's nature. Also, the options for createEntityAdapter
are a little different from those of @ngrx
and RTK
.
idSelector
: In@ngrx/entity
andRTK
, this is like theselectId
option. This only accepts a function that takes an entity and returns the value that should uniquely identify that entity.sort
: In@ngrx/entity
andRTK
, this is like thesortComparer
option. This only accepts a function that takes two entities and returns a number that will be used to sort them.
getActions
In both the @ngrx/entity and RTK implementations, the EntityAdapter has the actions as direct methods of the adapter. This is not possible here because, to do so, we would need the store information, but to create the store, we need the adapter information. Because of this, the actions are generated by a getActions
method.
With this approach, we have an additional advantage. In Zustand, we usually have the state and actions together, although it also allows using no-store actions. By externalizing the action generation, you can use either approach to configure your store.
- Using state and actions together:
const adapter = createEntityAdapter<User, string>();
type StoreState = ReturnType<typeof adapter.getState> & ReturnType<typeof adapter.getActions>
const store = create<StoreState>((set) => ({
...adapter.getState(),
...adapter.getActions(set),
}));
- Using external actions:
const adapter = createEntityAdapter<User, string>();
const store = create(adapter.getState);
const actions = adapter.getActions(store.setState);
Although the actions generated are more or less the same as in both @ngrx/entity
and @reduxjs/toolkit
, you need to consider that here, actions only need the payload to be used.
actions.addOne({ id: uuid(), name: 'John Doe' });
getSelectors
In this initial version of the library, the adapter doesn't have a concept of what a slice is, and because of this, neither do the selectors. The getSelectors
method creates the same selectors @reduxjs/toolkit
generates with adapter.getSelectors()
, but it doesn't allow any argument to compute the property; therefore, the selection will always be from the root of the state.
getState
Because Zustand allows adding properties more easily than Redux, instead of having a concept of "initial state", getState
only has the state of the entity adapter. If you want to add more properties to the store, just do so.
const adapter = createEntityAdapter<User, string>();
interface StoreState extends ReturnType<typeof adapter.getState> {
selectedUser: User | undefined;
}
const store = create<StoreState>(() => ({
...adapter.getState(),
selectedUser: undefined
}));
That's all folks!
I hope you like this library as much as I enjoyed developing it. In another post, I'll talk more about how to use the useEntityStore
function.
Thanks a lot!