lexich / redux-api

Flux REST API for redux infrastructure
MIT License
497 stars 88 forks source link

How to include loading/progress indicators? #164

Open morgler opened 7 years ago

morgler commented 7 years ago

Sorry for the stupid question, but it seems I have to write a lot of code to simply show a loading indicator with redux-api. Is there a recommended approach?

What I tried:

Approach 1: Show and hide a loading indicator depending on the state of the metadata loading attribute. This approach takes a lot of code, because each resource in redux-api has its own loading attribute.

Approach 2: Dispatch SHOW/HIDE actions to toggle the loading indicator. I implemented this using the prefetch and postfetch hooks.

    prefetch: [
      function({actions, dispatch, getState}, cb) {
        dispatch(showLoading())
        cb()
      }
    ],
    postfetch: [
      function({data, actions, dispatch, getState, request}) {
        dispatch(hideLoading())
      }
    ]

But again I have to do this for each resource separately, which adds a lot of duplicated code to my reduxApi definition. I also have to deal with the callback chain, which seems odd, if I want to handle a loading indicator.

How do you guys do it? There must be a simple way to show a loading indicator with redux-api, right?

lexich commented 7 years ago

Hi @morgler For show/hide loading progress there is project redux-api-react-switch

If you want to show global loading progress, maybe the best to catch all redux-api actions in reducer

function(action, state) {
  if (/^@@redux-api*_(success|fail)/.test(action.type) {
     state = hideLoading(state)
  } 
  ....
}

This solution isn't very beautiful, but efficient.

morgler commented 7 years ago

Thanks for the tips. Something like redux-api-react-switch is NOT what I want. I don't want to scatter loading-bar code among all my components. I prefer to have this loading-bar behavior capsuled into one component and never have to think about it again.

Your second suggestion sounds nice, though not very beautiful as you noted ;). The code that actually catches the event with refined regex (you forgot a ".") is:

const loadingBar = (state = {}, action) => {
  if (/^@@redux-api.*_(success|fail)$/.test(action.type)) {
    hideLoading()
  } else if (/^@@redux-api.*/.test(action.type)) {
    showLoading()
  }

  return state
}

However, this gives me the problem of having to dispatch actions from within a reducer – which is usually not a good practice. Sure, this thing is not a "real" reducer, but still. Anyway, this seems to be the solution I will go with.

Overall I find it rather amazing, that including a loading indicator has never been an issue for anyone else when using redux-api. Is there really no better solution?

morgler commented 7 years ago

What I ended up doing now as a workaround is this:

const defaultOptions = {
  transformer: function(data, prevData, action) {
    data || (data = {})
    return data.data
  },
  prefetch: [
    function({actions, dispatch, getState}, cb) {
      dispatch(showLoading())
      cb()
    }
  ],
  postfetch: [
    function({data, actions, dispatch, getState, request}) {
      dispatch(hideLoading())
    }
  ]
}

export default reduxApi({
  bands: Object.assign({}, defaultOptions, {
    url: `/api/bands.json`
  }),
  …
})

So I factored all the default options into a hash that I simply merge into every endpoint definition in reduxApi. To me this looks like the most elegant solution for now. My API definition stays clean and readable and I don't have to mess with regex or stuff polluting my store outside of my API definition :).

vitexikora commented 6 years ago

I am using this reducer:

import assign from 'lodash/assign'
import rest from 'api/rest'

/**
* This reducer watches all rest actions and produces an object like
* {action1: false, action2: false, activeAction3: true, ...}
* so you can read current syncing state anywhere like state.dataSyncing.YOUR_ACTION
*/

const allHandles = Object.keys(rest.actions)
const initialState = allHandles.reduce(
  (res, val) => {
    res[val] = false
    return res
  }, {})

export default function dataSyncing(state = initialState, action) {
  if (typeof action.type !== 'string' || action.type.indexOf('@@redux-api@') !== 0) return state
  const match = action.type.match(/^@@redux-api@(.*?)(_(?:success|fail))?$/)
  const handle = match[1]
  if (allHandles.indexOf(handle) >= 0) {
    const syncing = typeof match[2] === 'undefined'
    return assign({}, state, {[handle]: syncing})
  } else {
    return state
  }
}