Approach of handling cascade updates in normalized entities #4797
Replies: 2 comments 3 replies
-
I don't understand what you're asking or suggesting here, or why you're saying that several docs pages would need to be changed or deprecated. |
Beta Was this translation helpful? Give feedback.
-
Let's say I have a slice with several entities: author, post and comment. In TypeScript types, the normalized version of them would look like this: type AuthorId = string;
type PostId = string;
type CommentId = string;
interface Author {
id: AuthorId;
name: string;
comments: Array<CommentId>;
posts: Array<PostId>;
}
interface Post {
id: PostId;
authorId: AuthorId;
name: string;
text: string;
date: string;
comments: Array<CommentId>;
}
interface Comment {
id: CommentId;
postId: PostId;
authorId: AuthorId;
text: string;
date: string;
} If a user adds a new comment to a post, the changes should be reflected not only in the comment entity, but in author ( In other state management libraries there are no solutions like Redux-ORM, but still the libraries are used. My guess, that when using them, developers would not store the relation fields ( So why not recommend this approach in Redux too? Types for "new" version of normalized entities would look like this: type AuthorId = string;
type PostId = string;
type CommentId = string;
interface Author {
id: AuthorId;
name: string;
- comments: Array<CommentId>;
- posts: Array<PostId>;
}
interface Post {
id: PostId;
authorId: AuthorId;
name: string;
text: string;
date: string;
- comments: Array<CommentId>;
}
interface Comment {
id: CommentId;
postId: PostId;
authorId: AuthorId;
text: string;
date: string;
} This changes the way of how the normalized entities look like, and that's why I was talking about deprecation of some pages (point 1 and 2). Well, maybe deprecation is too much, and only a small adjustment is needed. In this case, here's how selection of an author's comments would look like: const selectCommentsByAuthorId = createSelector(
[
(state: RootState) => {
return state.application.comments;
},
(state: RootState, authorId: AuthorId) => {
return authorId;
},
],
(
comments,
authorId,
) => {
return comments.filter((comment) => {
return comment.authorId === authorId;
});
},
); The const selectCommentIds = createSelector(
[
(state: RootState) => {
return state.application.comments;
},
],
(
comments,
) => {
// Can't use `Object.keys(comments)`, because IDs can be numbers, which should be preserved.
return Object.values(comments).map((comment) => {
return comment.id;
});
},
); or by using It means, that
This is related to the point 3. And if this way is accepted as recommended, the documentation should mention it, which is the point 4. Please, let me know if further clarification is necessary. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I was looking for a way to cascade update normalized entities in Redux. It looks like only Redux-ORM is capable of that. And it is not maintained.
I was curious of how this problem is addressed in other popular state management JavaScript libraries, and asked GitHub Copilot about that (I've checked the documentations of the libraries myself, and the provided information looks correct).
The following libraries are covered:
Full answer
1. TanStack Query (React Query)
setQueryData
for related queries, but you must manage relationships and dependencies yourself.Example:
If you update a user, and want user’s posts to update, you invalidate the posts query, so it refetches.
2. Zustand
Example:
If you update a user, and want to update posts with the user’s info, you do it in your update function.
3. Jotai
Example:
If you have a user atom and a posts atom, a derived atom can recompute posts when the user changes.
4. XState
Example:
On a user update event, you can run an action that also updates posts in the context.
5. Valtio
Example:
If you update a user in a proxy object, you must also update related post proxies if needed.
6. nanostores
Example:
If you update a user store, a derived posts store can recalculate, or you can manually update related stores.
Summary Table
Key Takeaways
If you want automatic, normalized relationship management, Redux-ORM is still the most "ORM-like" solution in the JS state ecosystem. Other libraries maximize flexibility but require manual management of relationships and cascading updates.
Link to conversation.
In conclusion, either developers have hard times dealing with this issue, or they don't have it, because there is no a good universal solution or approach yet. I suspect, that the second case is the right one, and here's why.
The relation values can be calculated rather than stored and synchronized. For example:
allIds
/ids
arraycomments
array with comment IDs in a post entity)The approach is to keep the unique data in state, and derive/calculate/select other values from it. In Redux, it can be achieved by using selectors (with
createSelector
in particular).In issue #2771, there is a comment with a link to an article "Redux Patterns: Rethinking
byId
andbyHash
Structures", to which Mark left the following comment:So points 1 and 2, applied to relations between entities, is what I'm talking about, and the 3 point can also be covered by a selector.
With all that said, the question is could this approach be considered a new recommended approach, or the existing one still has significant advantages (what, exactly?) over it? Because if it could be, the following changes would be required:
createEntityAdapter
, so that it doesn't calculate theids
array and store it in Redux, but rather do in the generated selectorsBeta Was this translation helpful? Give feedback.
All reactions