Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structuring state management store (ngrx/redux). Flat as representative of data, or nested as representative of view?

Tags:

redux

state

ngrx

I'm using ngrx store, to maintain application state, normalizr to flatten my data from API calls and Immutable. So far it's been working really well, but I'm getting to some more complex data relationships and I was wondering how to proceed with structuring the store.

To simplify things, I have two sets of objects. Sessions, and Invoices.

One user logs on and can view his list of Sessions. The state in the store is held in an object ISessions:

export interface ISessions extends Map<String, any> {
  result: List<Number>;
  entities: {
    sessions: Map<Number, ISessions>,
    clients: Map<Number, IClient>,
    tutors: Map<Number, IProfile>
  },
  adding: boolean;
  loading: boolean;
  loadingFailed: boolean;
  error: string;
}

(entities contain the normalized output - clients and tutors are the nested types contained in sessions)

This works really well. The reducer sets it to loading so the view can display loading bars, then the data is populated I can use it in a sensible flat mannor referencing id's in the mapped data.

They can load invoices, this works very similarly around an IInvoices object:

export interface IInvoices extends Map<String, any> {
  result: List<Number>;
  entities: {
    invoices: Map<Number, IInvoice>,
    clients: Map<Number, IClient>,
    tutors: Map<Number, IProfile>
  },
  adding: boolean;
  loading: boolean;
}

So my store looks like this:

export interface IAppState {
  sessions: ISessions;
  invoices: IInvoices;
}

However, now I've come to the more complex relationship. The Sessions are assigned to Invoices. There's a few ways of going forward:

  1. Each of the Invoices could themselves have an ISessions object. This seems to go against the idea of having the data structure flat. I'd also likely have repeat sessions, stored in the AppState.sessions and the AppState.invoices. However it would be easier to manage as the IInvoice more directly maps to the state of the view (loading the sessions etc is stored in the invoices ISessions object all encapsulated).

  2. I could store a map of ISessions to invoice IDs in a the store separately to the ISessions and invoices:

eg:

export interface IAppState {
  sessions: ISessions;
  invoices: IInvoices;
  invoicesSessions: Map<number, ISessions>;
}
  1. I could load the invoices into the existing ISessions object. This means all the sessions data is in the same place, and there's no repetition. One issue with this is I now have difficulty mapping it to the view. Each invoice would have to get the list of sessions and filter out to only what it needs. I'd have to start tracking the loading of invoice sessions in the ISessions object.

There's also the question of whether I should be storing two separate lists of clients and tutors, one in ISessions and one in IInvoices. Is splitting the store like this a bad idea? This would mean my reducers would have to all operate on the whole IAppState object rather than the sub-sections.

Basically: When I get data in, should I be stripping it out and compiling big ID-indexed lists, letting the view's then almost 'query' what they need - basically using the store like a database OR should I be holding a deep-nested collection of objects that directly reflect views - meaning data is often repeated where it's needed multiple times?

like image 462
Joe Avatar asked Feb 26 '17 18:02

Joe


1 Answers

Yes, the recommended approach for structuring your Redux store is to build your state shape in terms of your data, not your views, and the recommendation for relational data is to keep it all normalized. Use selector functions to act as queries into that state.

For more info, see:

  • FAQ: Organizing relational data?
  • Structuring Reducers - Normalizing State Shape
  • React/Redux Links: Selectors and Normalization

Also, my "Practical Redux" tutorial series shows how to use the Redux-ORM library to manage relational data in your Redux state: Practical Redux-ORM Basics and Practical Redux, Part 2: Redux-ORM Concepts and Techniques.

like image 59
markerikson Avatar answered Oct 26 '22 08:10

markerikson