These applications almost always have a hierarchical structure. Let's take the list of posts on Hexlet as an example. Each post has an author and comments, which in turn have likes. This data can be represented as follows:
const posts = [
{
question: 'How do you write code?',
likesCount: 2,
comments: [
{
answer: 'Open the editor!',
likesCount: 1,
createdAt: '11-12-2022',
},
{
answer: 'Sitting down!',
likesCount: 3,
createdAt: '11-12-2022',
},
]
},
{
question: 'Which is better: vim or emacs?',
likesCount: 2,
comments: [
{
answer: 'FAR the best!',
likesCount: 100,
createdAt: '11-12-2022',
},
]
}
];
The hierarchical representation of data reflects their structure well. You can see what's what at a glance. The data is easy to display and quite easy to change. Especially if the output on the screen is the same as their structure, and the data do not overlap with each other. Hexlet's topics are just such an example. Each topic lives its own independent life (there are some dependencies, but they don't concern the data themselves).
However, if the data are linked, the hierarchical structure becomes a problem. Imagine having to output the last 10 comments. How do you do that? I'll have to go through all the threads, take all the comments, combine them and look for the most recent ones. A frightening example:
const comments = posts.flatMap((p) => p.comments);
const sortedComments = comments.sort((c1, c2) => new Date(c2.createdAt) - new Date(c1.createdAt));
const lastComments = sortedComments.slice(0, 10);
The situation gets even worse when there are many-to-many connections. In this case, it is not clear what to put where. And there will always be situations where the resulting structure is not the best.
One way out of this situation is to start duplicating data. I.e., creating additional structures optimized for specific tasks. And while it makes sense in general, manually maintaining these structures will do no good. And in these databases, it's the databases themselves that are responsible for generating indexes. We as programmers don't have to worry about that. And here you have to implement additional synchronization in all stages, whether that's adding, changing and deleting.
Another way is to normalize the data, just like in relational databases. Imagine them as flat arrays. For example, like this:
// In a real application, everything will be stored in a single state object
const posts = [
{
id: 3,
question: 'How do you write code?',
likesCount: 2,
},
{
id: 100
question: 'Which is better: vim or emacs?',
likesCount: 2,
}
];
const comments = [
{
id: 1,
postId: 3,
answer: 'Open the editor!',
likesCount: 1,
},
{
id: 8,
postId: 3,
answer: 'Sitting down!',
likesCount: 3,
},
{
id: 3,
postId: 100,
answer: 'FAR the best!',
likesCount: 100,
},
]
If there are no clear boundaries between the data, and they depend on each other, then this structure is much more convenient. It easily allows you to perform general aggregations and special output options. It's important that the normalized data are not duplicated. In a well-organized state, data doesn't get repeated more than once.
Sometimes the opposite of normalization is used - denormalization, but this is more about the backend than the frontend.
However, when one door opens, another must close. Normalization will simplify everything in one place, but complicate it in another. Now you have to write the following code to extract the comments from a particular topic:
const postId = /* post identifier */;
const commentsForPost = comments.filter((c) => c.postId === postId);
There is no more code here than when choosing a particular post. But it's more complicated in an algorithmic sense, and more resources are spent on it. Whether this is a problem or not is an open question. As a rule, it isn't Frontend development very rarely operates with large quantities, such as tens or hundreds of thousands. Most often, the size of collections is limited to a hundred or so items.
To summarize, most mechanisms for storing state on the frontend recommend using the second storage method. And it doesn't matter if it's done within a framework or not. This approach is easier to scale and works well for all situations. Whereas the first approach will create a lot of problems the moment the data structure ceases to coincide with how it's displayed.
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.