The idea of modularized unified management of request data
This article discusses how to manage data updates in complex business scenarios with multiple independent components. It suggests using a centralized data layer and libraries like Zustand or Jotai for better data management and more precise UI rendering.
In a complex business scenario, a page is split into multiple components, or multiple components together form an independent module without being affected by other modules, as shown in the following figure.

The typical page shown above has three modules: asset list in the sidebar, asset detail in the middle, and related asset on the right. The data independence of each module is managed by a Query. Here we take the popular React Query as an example.
However, we found that although these three modules are independent, their data are interconnected, as shown below.

There seems to be nothing wrong with managing the data of each module in the current way. After all, there is no need to worry about the problem of data duplication. It can render the correct UI after the data is generated. This is the simplest way to create a static page.
Now with the addition of new requirements, it is necessary to optimistically update the data in real time. That is, when the backend data is updated, the corresponding data on the frontend needs to be updated as well.
At this time, when using React Query, optimistic updates are performed on the corresponding data of the three modules' Queries.

Now the business scenario is upgraded. User attributes are added to each asset, and the user also needs to be reflected in the UI in real time. Due to the addition of the user, the asset and user data in query 1 are now coupled, and one user will correspond to multiple assets. So the current directly embedded user data must be redundant. So when updating the data, how should I update it? I may have to traverse all of them to determine whether to update. Such a data structure is the simplest for the backend. After all, the user is a reference data, and the backend only needs two selects or directly join tables in the database query. For the front-end of the real-time data-driven UI, such data is indeed a nightmare for optimistic updates or partial updates of data. The business scenario in the figure below only has three modules, and the actual business is far more complex than the figure below.

So, in actual scenarios, is there any way to solve this problem?
First, the backend and frontend need to reach a consensus to eliminate redundant data. In other words, the backend cannot directly throw the data generated by the database table connection to the frontend. Instead, an object is set up in the request to specifically host these reference data.
For example, the following data:
{
"data": [
{
"user": {
"id": "1234ax",
"name": "John Doe"
},
"id": "ax4ax",
"name": "Fake"
},
{
"user": {
"id": "1234ax",
"name": "John Doe"
},
"id": "ax4ax",
"name": "Fake 2"
}
]
}
Transformed into data like this:
{
"data": [
{
"userId": "1234ax",
"id": "ax4ax",
"name": "Fake"
},
{
"userId": "1234ax",
"id": "ax4ax",
"name": "Fake 2"
}
],
"objects": {
"users": {
"1234ac": {
"id": "1234ax",
"name": "John Doe"
}
}
}
}
This eliminates redundant data and makes the data hierarchy clear. dataThe data in is what we query, and other data referenced in it is placed in objects. objectsOther data types can be expanded later.
The front end needs to manage the data requests of each module in a centralized data layer. We can simply use zustand or jotai to establish table management for each data object. Here is a simple code example.
import { useShallow } from "zustand/react/shallow"
import { create } from 'zustand'
type Id = string
export type CollectionType<T extends { id: string }> = Record<Id, T>
const useAssetStore = create<{
data: CollectionType<AssetModel>
}>(() => {
return { data: {} }
})
export const {
add: addDrips,
patch: patchDrip,
remove: deleteDrip,
get: getDrip,
getAll: getAllDrips,
} = baseCollectionMethodsFactory(useAssetStore) // baseCollectionMethodsFactory 作为你的 collection 方法扩展
export const useAssetSelector = <Value>(
id: string,
selector: (data?: AssetModel) => Value,
) => {
return useAssetStore(
useShallow(({ data }) => {
const drip = data[id]
return selector(drip)
}),
)
}

When asset updated eventthe event is triggered, we no longer need to care about the managed queries scattered in various modules, find the queries and then update them, but directly operate on the data in the data collection. This way, we will not miss anything, and with the help of zustand/jotai, we can render the UI in a more fine-grained manner.