Handle downloaded item normalization in redux-saga or reducer?
Asked Answered
T

2

6

The data I get from the remove API is not in a format that my app can handle. My saga downloads the data.

Who should handle the normalization?

The saga itself before dispatching the success action with the normalized data?

Or should the router normalize the date before building the new state?

Edit I opted to normalize in the saga and keep the reducer clean. It just replaces the activities with the new ones activitiesUpdated gives it.

The reducer

export default function account(state = ACCOUNT, action) {
  switch (action.type) {
    case "account/LOGIN_SUCCESS":
      const { access_token, user } = action
      return { ...state, user, access_token, authenticated: true, error: "" }
    case "account/LOGOUT_SUCCESS":
      return ACCOUNT
    case "account/LOGIN_ERROR":
      return { ...state, error: action.error }
    case "account/ACTIVITIES_UPDATED":
      return { ...state, activities: action.activities }
    default:
      return state
  }
}

And those are the sagas:

function sortActivities(activities) {
  return action.activities.sort((a,b) => b.timestamp.localeCompare(a.timestamp))
}

function addInvoices(activities) {
  let lastYearMonth, invoiceItem
  return activities.reduce((state, item, index) => {
    const currentYearMonth = item.timestamp.substr(0,7)
    if (currentYearMonth != lastYearMonth) {
      lastYearMonth = currentYearMonth
      invoiceItem = {
        id: currentYearMonth,
        type: "invoice",
        parking: 0,
        rebates: 0,
        sum: 0,
        timestamp: currentYearMonth
      }
      state.push(invoiceItem)
    }
    const amount = Math.abs(Number(item.gross_amount))
    if (item.type == "parking") {
      invoiceItem.parking += amount
      invoiceItem.sum -= amount
    } else if (item.type == "rebate" || item.type == "surplus") {
      invoiceItem.rebates += amount
      invoiceItem.sum += amount
    }
    state.push(item)
    return state
  }, [])
}

function *getActivities(access_token) {
  console.info("fetch activities")
  try {
    const activities = yield call(getActivitiesAsync, access_token)
    console.info("activities fetched")
    yield put(activitiesUpdated(addInvoices(activities.sortActivities(activities))))
  } catch (error) {
  }
}

function *updateActivities() {
  while (true) {
    const { access_token } = yield take(LOGIN_SUCCESS)
    console.info("Calling getActivities")
    yield call(getActivities, access_token)
    while (true) {
      const {type } = yield take([REFRESH_ACTIVITIES, LOGOUT])
      if (type == LOGOUT) {
        break
      }
      yield call(getActivities, access_token)
    }
  }
}

When you think about the double wrapped while loops in the updateActivities saga?

Also is it correct that

yield take([REFRESH_ACTIVITIES, LOGOUT])

is just a shortcut for

yield race[take(REFRESH_ACTIVITIES), take(LOGOUT)]

Tabanid answered 2/3, 2016 at 20:39 Comment(0)
M
1

Ultimately you're free to do whatever works for you in this case--there isn't a strong case for one over the other. You may end up finding, depending on how the data is structured, that doing it in the saga will have less code because you're only taking apart the result once vs twice (once in each of 2 reducers that care about the data. But this may or may not be the case. I also like the idea of doing it in the reducer because reducers should typically be as simple as possible, and this model fits with that.

But like I said, I don't think there's a strong generalized argument for one over the other.

Misestimate answered 2/3, 2016 at 21:57 Comment(1)
I have updated my question with code I am using atm.Tabanid
T
-1

Also you can use createSelector for data normalization, to keep sagas clean:

- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
- Selectors are composable. They can be used as input to other selectors.
Thersathersites answered 9/7, 2021 at 3:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.