Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux - How to organize store in big application [closed]

We're developing SPA using Angular 7 in a combination with NGRX. We have migrated to NGRX a few months ago to use mainly for authentication and session data. After that, we have started moving other functionalities as well. I will describe the problem and what solutions we have in our heads and their drawbacks. I would be very thankful to hear how you solved this problem(if you had) or ideas on how to solve having problems.

Motivation to use Redux(NGRX)

  1. We had data that we needed to share between multiple components. Sharing through properties was not possible since we had components separated by Angular router.
  2. Reduce number of requests by reusing loaded data like Auth/Session
  3. Performance - Angular performs considerably faster when the component is using ChangeDetectionStrategy.OnPush

Business problem

Inside our application, we have 6 modules. Each module has at least 10 features(pages). We have just few common details shared between them.

First steps with Redux Store

Since our main problem was #1 and #2(described above), we started with following store structure:

 {
  "authentication": {
    "isLoading": false,
    "error": {},
    "token": "auth-token"
  },
  "session": {
    "userDetailsIsLoading": false,
    "userDetailsData": {},
    "userDetailsError": {}
  }
}

So far, so good. It was simply like all Redux Tutorials with increase/decrease/reset counter.

Migrating modules features

Here the fun began since we have a lot of features inside app (6 modules * 10 features = 60+). The first decision was to use reducer per feature and not reducer per entity because we have pages with the same type of entity, but fetched with different filters. We're filtering data on the server side, because of a big amount of data. Ex: Recent 10 posts, Top 10 posts - both on the same page

So, we have extended our store with more reducers:

{
   "authentication": {},
   "session": {},
   "blogs-list": {},
   "blogs-add": {},
   "administration-users": {},
   "administration-add-user": {}
}

Problems

Action type collision

Yes, we had first type collision as we were using format [Feature] Action Description. To solve it, we have decided to add use a namespace [Module][Feature] Action Description.

Big store

That's the biggest problem. The entire store becomes very big. It's not a problem when the store is clean, but after a few navigations, the store contains a lot of unneeded data.

Solutions which we considered

Lazy loading

Angular allows to lazy-load so-called NGRX Features. This means we can start with just shared data(auth, session) and once a module is opened, all reducers are added to the store. This solution solves the problem partially. If you navigate to all modules, the store becomes big again.

Switching to entity reducers

This looks easy in theory, but hard in practice. As I mentioned, we have a kind of dashboard where we display the same entities but fetched with different filters + pagination. In the head I have an idea to have different reducers for these cases, like:

{
   "latest-entities": {
       "isLoading": bool,
       "data": {},
       "error": {},
   }, 
   "most-popular-entities": {
       "isLoading": bool,
       "data": {},
       "error": {},
   }
}

but there we have another drawback. We used to keep filters in feature-store and once we change filters, effects are triggering data reload. Switching to this architecture, we move the responsibility of data reloads to the containers. Container will be responsible for dispatching actions to both stores latest-entities and most-popular-entities. This solution will also get bigger and bigger. The problem couldn't be fully solved by reorganizing reducers and store.

Don't move features to Redux but keep them in containers

We can keep feature logic in containers and use redux only for cross-module data like a session, auth, and notifications. This is also a solution but has its drawbacks. I would like to keep the application consistent and not to have business logic in both containers and store.

Reset stores

To keep the store smaller, we can create an action that will reset the store to the initial state which will be dispatched once the feature is not used. Here is an example:

  public ngOnDestroy(): void {
    this.store$.dispatch(new BlogStoreActions.DestroyAction());
  }
like image 439
Maxian Nicu Avatar asked Jan 15 '20 21:01

Maxian Nicu


People also ask

How does Redux handle large data?

As an app gets large and complex, you might want to store large, bulky data inside your Redux store and access it inside a component. A Redux store doesn't have a limit on the amount of data stored, so you can pretty much use it to store almost anything, including bulky JSON data, data in table form, etc.

Where is the entire state kept in Redux?

There is a central store that holds the entire state of the application. Each component can access the stored state without having to send down props from one component to another. There are three core components in Redux — actions, store, and reducers.

What's the maximum memory size of the Redux store?

This Data can be about 70-80 KB but I think average size of each user will be 30-40 kb. This data is modified with combined of 5-6 reducers and 30-50 actions. I have one component which uses all of this data and 10-15 components which uses some part of it.

Should I keep all component's state in Redux store?

There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.


1 Answers

I can't comment yet, but your question had me curious so I posed it to twitter and one of the ngrx maintainers.

Here was the response (from Wes Grimes):

If the store gets too big then maybe it’s time to reconsider what data is fetched client side. Do you really the full data model or would a paired down view model suffice? Fetch data in batches bits at a time. Get 50 and then go back for 50 more as you need it (lazy load).

Also take advantage of lazy loaded feature modules in angular and use the forFeature with NgRx and then only load the portion of the store needed. But again. If size is a concern, consider minimizing client side data and offload the heavy lifting to the backend.

Link:

https://twitter.com/qkwrv/status/1217593781282734080

like image 88
Jesse Avatar answered Oct 13 '22 11:10

Jesse