Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle downloaded item normalization in redux-saga or reducer?

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)]

like image 598
philk Avatar asked Mar 02 '16 20:03

philk


1 Answers

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.

like image 146
Nathan Hagen Avatar answered Oct 29 '22 12:10

Nathan Hagen