Data transformation in Class vs Reducer (ngrx/redux)
Asked Answered
C

0

5

Using ngrx/redux, and following the ngrx example app as close as possible. Now I'm doing this somewhere in the application:

get totalItems (): number {
   return sumBy(this._data.items, 'metadata.value');
}

Where the _data is the raw async data from a remote server, and is part of some data of a User.

Now I have some options:

1. Create a Class User

constructor (public _data: UserResponse) {}

And have some methods like:

get name (): string {
  return this._data.name;
}

get address (): string {
  return this._data.address;
}

And additionally things like the aforementioned totalItems, and other shortcuts to get a different result than the raw data. What I don't like is the repetition of the properties, and where to use this class;

  • Do I use the User Class in the api-service, and return the transformed data to the the @Effect module, which in turn will trigger the SUCCESS or FAILED actions?
  • Do I put it in the @Effect module, right before the action triggers?
    • Do I use extends or implements on the angular component?

Another thing is that we apply the transformed data on the payload property, which would make it hard to debug the raw remote data.

2. Another option would be to transform this data inside the reducer:

The current reducer:

case ActionTypes.FETCH_USERS_SUCCESS:
   return {
     ...state,
     users: action.payload
   };

What I could do:

case ActionTypes.FETCH_USERS_SUCCESS:
   return {
     ...state,
     users: action.payload.map((user) => {
        return({ 
          ...user, 
           totalItems: sumBy(user.items, 'metadata.value' });
     })
   };

Is this an anti-pattern or bad practice? Because I see .filter being used in examples all the time, and I don't see summing up as being impure, so what would be the arguments for/against doing it in the reducer? This way I could eliminate needing a User class, and load all the necessary data into a component.

And, keeping the reducers from filling up with a lot of logic, perhaps make a separate reducer for this, which "listens" to the same actions.

Option 3:

Put it in a selector:

export const selectUsers = createSelector(selectUsersState, (state: Userstate) => state.payload.map((a: UsersResponse) => new User(a)))

Reading this answer by @dan_abramov: How to put methods onto the objects in Redux state? implies me that the 2nd option makes more sense for this, as he states:

"In Redux, you don't really have custom models. Your state should be plain objects (or Immutable records). They are not expected to have any custom methods."

Chickamauga answered 19/4, 2017 at 7:31 Comment(7)
#41445582Padlock
@olsn, can you explain the difference between filtering in a reducer, and summing up a property (still pure?)Chickamauga
There is no difference - because the point is, that in both cases data-operations are performed where data is being mutated/created/deleted - and the ngrx-pattern states that the reducer should only store the data and not do any transformations - technically you are free to do whatever you want - and I would also advice you to do what's best for your project and not follow any rule blindly - but you were asking for the pattern/anti-pattern, and your way would be an anti-patternPadlock
Which way? the 1st or 2nd?Chickamauga
I was referring to the 2nd way, but the first one is as well againt the pattern of a serializable redux-state (as you already saw in the answer of @dan_abramov) - as a personal advice: Option 1 => nogo! dont do it! it will most likely cause trouble sooner or later - Option 2: even though it's against the pattern - you will probably be fine using that solution - if you want to go "by the book" - use @ngrx/effects (this will however add quite some complexity and a lot more code to your app)Padlock
@olsn, thanks, I was leaning towards putting it in the @Effects, but then I still kind of have problems with having a Class model in the store, which I don't know if that's a good or bad idea...Chickamauga
Classes in the store are never a good idea ;-) - think of the store of something that should be serializable and deserializable in 1 or 2 lines of code to persist the state or undo and redo things with a minimum amount of codePadlock

© 2022 - 2024 — McMap. All rights reserved.