Photo by Tobias Fischer on Unsplash
Sooner or later, all frontend developers need to save relational data in a Redux store.
TL;DR
Follow the advice of Firebase, they are very useful.
Data
Whether they come from APIs or static payloads, based on relational DBs or not, the data has relationships between them.
We will define resources all those types of data that could have relations with other resources.
An example resource could be a post from our personal blog.
This post will have a 1:1 relationship with the author (from the point of view of the post, we will see that an author will have a 1:N relationship with posts) and a 1:N relationship with comments.
An inefficient structure
Let's say that our personal blog should only present a list of posts and a detail page for each of them.
For this specific example we could have, in our Redux store, a first level key containing all our posts indexed by id. Within each one we could nest the author's data and comments.
const store = {
posts: {
'p123': {
id: 'p123',
title: 'Best React pattern in the world!',
author: {
id: 'a123',
name: 'Francesco',
email: 'name.surname@gmail.com',
},
comments: {
'c123': {
id: 'c123',
body: 'Best article ever!',
},
'c124': {
id: 'c124',
body: 'Best article ever ever!',
},
}
},
}
}
// And so on, you get the point
The problem occurs if I wanted to create a page for the author's profile with a list of his posts.
To take an example with Redux selectors, we could retrieve the data we need in this way:
// This returns an array of posts
const getPostsByAuthor = authorId => state => (
Object.values(state.posts).filter(post => post.author.id === authorId)
)
// And you'd call this selector like this:
const state = store.getState()
const postsByAuthor = getPostsByAuthor('a123')(state) // [...]
But it would be particularly inefficient to be able to get what we need: every time we should go through all the posts.
A weighted structure
A weighted structure could be a 1:1 representation of the hypothetical tables within our relational DBs.
const store = {
posts: {
'p123': {
id: 'p123',
title: 'Best React pattern in the world!',
author: 'a123',
},
},
author_posts: {
'a123': ['p123'],
},
authors: {
'a123': {
id: 'a123',
name: 'Francesco',
email: 'name.surname@gmail.com',
}
},
post_comments: {
'p123': ['c123', 'c124'],
},
comments: {
'c123': {
id: 'c123',
body: 'Best article ever!',
post: 'p123',
},
'c124': {
id: 'c124',
body: 'Best article ever ever!',
post: 'p123',
},
},
}
In this case we removed the nesting problem. However, we added two new first level keys to our Redux store.
This approach is not totally wrong but, as our application grows, it may be difficult to manage all relationships efficiently.
It could be a usable approach if the amount of resources is limited. But it is also true that, if the amount of resources is limited, we may not really need Redux.
An efficient structure
Following the recommendations of Firebase, we could save ourselves some first level keys:
const store = {
posts: {
data: {
'p123': {
id: 'p123',
title: 'Best React pattern in the world!',
author: 'a123',
},
},
comments: {
'p123': ['c123', 'c124'],
},
},
authors: {
data: {
'a123': {
id: 'a123',
name: 'Francesco',
email: 'name.surname@gmail.com',
},
},
posts: {
'a123': ['p123'],
},
},
comments: {
data: {
'c123': {
id: 'c123',
body: 'Best article ever!',
post: 'p123',
},
'c124': {
id: 'c124',
body: 'Best article ever ever!',
post: 'p123',
},
},
},
}
Unlike Firebase, we are not going to nest relationships with "placeholders".
Instead, we organize our first-level keys as small second-level store containers.
Do you think of something similar thinking about the reducers
and the combineReducers
function? Same logic: we reduce our global object to the smallest representable portion.
Bonus: how to structure selectors
After having structured our Redux store, the first question that could come to your mind could be: how do I get this data?
Here are some simple selectors.
// Base data
const selectAuthors = state => Object.values(state.authors.data)
const selectAuthor = id => state => state.authors.data[id]
const selectPosts = state => Object.values(state.posts.data)
const selectPost = id => state => state.posts.data[id]
// Totally useless
const selectComments = state => Object.values(state.comments.data)
// Maybe useless
const selectComment = id => state => state.comments.data[id]
// Relations
const selectAuthorPosts = authorId => state => {
const authorPosts = state.authors.posts[authorId] || []
return authorPosts.map(postId => selectPost(postId)(state))
}
const selectPostComments = postId => state => {
const postComments = state.posts.comments[postId] || []
return postComments.map(commentId => selectComment(commentId)(state))
}
Conclusion
Now you could structure a Redux store to save relational data. It could be overkill in some cases but it will come in handy to handle more complex applications.
Leave a 🦄 if you liked the post!
Top comments (6)
Hey, great post!
Question about changing data in this kind of structure like adding/deleting children of a parent:
If I want to add a new comment , the comments reducer will add the comment object and the new comment ID to the relevant sections on the comments state, but what about the post state?
Something has to update the comments array in the relevant post state with the new comment ID.
if for example I render a table of posts with comments id's column, the new comment wont show.
Should I access the comments state and for each post row map to see which comments have this post id as parent
Or
Should I just add the comment ID to the comments array in the post state on creation so I won't even need to access the comments state when rendering the posts table?
how would you structure it?
Hey!
Did you see this library? it makes it easy to structure your store
also, it's described in redux docs
Hey Pavel!
Yes, I knew that library. However, it seems that its purpose is to, actually, normalize some nested data coming from some kind of source.
It could be a step before saving some data inside the kind of structure I described here. What do you think?
Yes perfect! i do use the same approach.
This structure feels much cleaner to me: gist.github.com/AliRain/606ac00d05...
This structure feels much more like a relational set of records to me too. But how would you access a single record without looping through the whole array? This sounds like a performance pitfall.