takarajapaneseramen.com

Understanding Redux Toolkit's createEntityAdapter: A Comprehensive Overview

Written on

Chapter 1: Introduction to createEntityAdapter

The official Redux Toolkit documentation on createEntityAdapter serves as a valuable guide, yet it often assumes users understand the underlying reasons for its necessity. This can be a challenge for many, and while the documentation briefly mentions the "why" in the Usage Guide, it quickly transitions into practical how-to examples. Therefore, let’s take a moment to explore why utilizing this utility can help standardize our approach to managing arrays of items.

Arrays in State

The documentation emphasizes the importance of createEntityAdapter in the context of API responses, which is quite pertinent. It’s common for an API to return an array of numerous records, often requiring us to manage this array as a complete set or to target specific items by their ID.

In my previous post, Honey, Where's My Array Item?, I discussed various methods for locating an item within an array. While I touched on the Big O notation of each approach, I may not have elaborated on its significance. To put it succinctly, minimizing the number of iterations through a collection enhances performance. When items are stored in an Object, we can access them directly via their properties, eliminating the need for looping altogether.

To illustrate this point, I created a jsPerf example comparing the efficiency of storing entities in an object by ID versus using different methods to iterate through an Array for entity retrieval.

The results clearly indicate that utilizing Object access is significantly faster. Thus, it makes logical sense to standardize our approach. If we frequently access entities by a specific property, it’s practical to store them in an object indexed by that property from the outset, which is precisely what createEntityAdapter achieves. It constructs a data structure featuring an Array of IDs (allowing us to track which entities we have and to maintain default ordering) alongside an Object that holds the entities, indexed by their IDs.

With this structure, we can easily retrieve an entity using entityState.entities[someId]. However, we might wonder about the usage of IDs and whether the adapter.upsertMany(adapter.getInitialState(), raw) function is overly complex for data storage. Indeed, it may seem that way, but this merely scratches the surface of what createEntityAdapter offers.

Customizing createEntityAdapter

createEntityAdapter accepts an optional parameters argument, which can include two properties: selectId and sortComparer. The selectId property should always be a function that identifies the property of the object to be regarded as the entity’s ID. However, things become particularly interesting when a sortComparer is provided. In this case, createEntityAdapter generates a different type of adapter that maintains the order of the IDs based on the comparison results.

It’s crucial to understand that the adapter and the data are distinct entities. Ignoring TypeScript nuances, if you had multiple collections using the same ID property and sorting criteria, a single adapter could serve all of them. Conversely, if you have an Array that requires a different sorting method and index property, you can run it through various adapters to yield entirely different EntityStates.

This leads us to the necessity of using upsertMany to populate our data from an Array. The adapter we created includes numerous CRUD operations designed to manage entities while preserving the sort order (if a sortComparer was supplied), and upsertMany is one such operation. Since the data exists independently from the adapter, it’s essential to provide initial data to "modify" with the CRUD functions. This is why we utilize adapter.getInitialState() when first populating our data — it will return {ids:[], entities:{}}. If data already exists, that would be used instead.

I put "modify" in quotation marks because, in Redux, we don’t actually alter anything. Instead, we return a new EntityState reflecting the requested changes applied to the original state.

In summary, employing createEntityAdapter allows us to create an object equipped with methods for transforming a raw array into an object for rapid access, along with an array of IDs that facilitates sorted data retrieval.

Accessing Entities

This brings us to the process of retrieving data from the EntityState that the adapter generates. You might expect the adapter to provide methods for accessing data similar to those for managing it, but that’s not the case.

Instead, the adapter features a getSelectors method. While this may seem excessive for many scenarios, the rationale behind it is straightforward. In Redux, you might not provide the EntityState directly to the selector. Instead, you could be passing the entire Redux state, and the selector needs to pinpoint the correct EntityState.

Consider a situation where you have both books and categories in your Redux store, both utilizing the same ID property and sorting criteria. You could retrieve the set of selectors for books with adapter.getSelectors((state) => state.books) and for categories with adapter.getSelectors((state) => state.categories). However, if you’re already in a context dealing with books, you could utilize the selectors returned from adapter.getSelectors() directly.

Once you have the selectors, you can feed one of them the data (and an argument, when necessary) to extract the desired information from the EntityState structure. For instance, you could add the following code to our CodeSandbox:

const { selectTotal } = adapter.getSelectors();

console.log(selectTotal(entityState)); // Outputs: 4

Here, we don’t require an argument for getSelectors because we created our EntityState directly and have it readily available — no need to search for it within Redux.

TypeScript Considerations

When I initially utilized createEntityAdapter in the context of RTK Query, I found it immensely helpful to specify the type of data that would be stored in that context. However, I must admit, it wasn’t immediately clear to me how to define the data type for an endpoint that transformed an Array into an EntityState using transformResponse. Let’s clarify that here with data similar to our previous examples.

Where to Go from Here

If you found this exploration valuable, you may appreciate additional resources on this topic. For those wishing to support dedicated authors like myself, consider subscribing through my link.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Understanding the Flow of HTTPS Requests in Spring Boot Apps

This guide clarifies how HTTPS requests are processed in Spring Boot applications, ensuring secure and efficient web development.

Ultimate Guide to Your Affordable Glow Up Journey

Discover how to enhance your life on a budget through personal growth and practical strategies.

The Complex Reality of Covid-19 Vaccines: Efficacy and Limitations

This article explores the complexities of Covid-19 vaccines, their effectiveness against illness, and the implications for public health.

Embracing Life's Transitions: Finding Growth in Setbacks

Explore how to navigate life's challenges by embracing change and finding growth amidst setbacks.

Delightful Connections: Exploring Poetry and AI on Medium

Discover the inspiring connections found on Medium, from poetry to artificial intelligence, through the lens of vibrant community interactions.

Mastering Key JavaScript Concepts for Front-End Developers

Explore three crucial JavaScript concepts for aspiring developers: event delegation, object keys, and debouncing.

Unlocking ADHD Mastery: Understanding Goals and Learning Styles

Explore how understanding ADHD and learning styles can facilitate mastery and self-improvement.

Embracing Creativity: The Power of Puppets and Crayons

Discover how adopting a playful approach can enhance communication and foster creativity in problem-solving.