TLDR; Don’t put UI logic into reducers instead put it into a separate reducer. When handling asynchronous actions in your application, most of the time you want to let users know that their request is being executed in form of some loading indicator. Adding UI logic to reducers is like mixing apples and pears, and is also against SoC (Separation of Concerns). Let’s first start with the usual (bad) way of handling loading actions In our reducer we declare and property. requestInProgress refreshing { newsActionTypes } ; initialState = { : {}, : , : , : , : }; newsReducer = { (type) { newsActionTypes.UPDATE_NEWS: newsActionTypes.DELETE_NEWS: newsActionTypes.PUBLISH_NEWS: newsActionTypes.FETCH_NEWS: { ...state, : payload.refreshing, : , : }; newsActionTypes.FETCH_NEWS_SUCCESS: newsActionTypes.PUBLISH_NEWS_SUCCESS: newsActionTypes.DELETE_NEWS_SUCCESS: newsActionTypes.UPDATE_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : , : , : }; newsActionTypes.PUBLISH_NEWS_ERROR: newsActionTypes.DELETE_NEWS_ERROR: newsActionTypes.UPDATE_NEWS_ERROR: newsActionTypes.FETCH_NEWS_ERROR: { ...state, : , : , : payload.error }; : state; } }; newsReducer; import from 'src/constants/store/actionTypes' const cachedNews lastUpdate 0 requestInProgress false refreshing false error null const ( ) => state = initialState, { type, payload } switch case case case case return refreshing requestInProgress true error null case case case case return cachedNews lastUpdate refreshing false requestInProgress false error null case case case case return refreshing false requestInProgress false error default return export default So every time we dispatch a action, is set to . And every time we dispatch a or action is set to . FETCH_NEWS requestInProgress true FETCH_NEWS_SUCCESS FETCH_NEWS_ERROR requestInProgress false All you have to do now is to check in your component where you are displaying news, if is true and render the loading indicator based on it. requestInProgress { fetchNews } ; NewsComponent= { {news}=props; {requestInProgress, refreshing, cachedNews}=news; useEffect( { props.fetchNews(); }, []); onRefresh = { refreshing = ; props.fetchNews(refreshing); }; (requestInProgress && !refreshing) { <ScrollView refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} > <News cachedNews={cachedNews}/> </ScrollView> ); } const mapStateToProps = state => ({ news: state.news, }); import from 'src/store/actions/newsActions' const => props const const => () const => () const true if return ; } return ( < = }/> Loader text "Loading news" When the component mounts, it will call the fetchNews action and a loading indicator will be displayed in the middle of the screen. If we swipe down when ScrollView is at onRefresh is called, which calls fetchNews, but this time we are refreshing news and the loading indicator is displayed above ScrollView. scrollY: 0, That’s all good for now, but where is the problem? The problem is that in our case news can be , , , , and to display loading indicators for each action on different parts of the screen we would need to have 5 flags, for each action one, in our newsReducer. And there is one more problem, having booleans in reducers for loading flags has its limitations. FETCHED PUBLISHED UPDATED DELETED REFRESHED What if you want to show a loading indicator on a specific news in the list? To achieve this, we will need to use, instead of boolean, an object that receives the id of the news being deleted. const initialState = { ... { , ’’} } deleteInProgress: inProgress: false newsId: Our newsReducer would end up looking like this. { newsActionTypes } ; initialState = { : {}, : , : , : , : , : { : , : }, : , : }; newsReducer = { (type) { newsActionTypes.FETCH_NEWS: { ...state, : payload.refreshing, : , : }; newsActionTypes.FETCH_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : , : , : }; newsActionTypes.FETCH_NEWS_ERROR: { ...state, : , : , : payload.error }; newsActionTypes.PUBLISH_NEWS: { ...state, : }; newsActionTypes.PUBLISH_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : }; newsActionTypes.PUBLISH_NEWS_ERROR: { ...state, : }; newsActionTypes.UPDATE_NEWS: { ...state, : }; newsActionTypes.UPDATE_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : }; newsActionTypes.UPDATE_NEWS_ERROR: { ...state, : }; newsActionTypes.DELETE_NEWS: { ...state, : { : , : payload.newsId } }; newsActionTypes.DELETE_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : initialState.deleteInProgress }; newsActionTypes.DELETE_NEWS_ERROR: { ...state, : initialState.deleteInProgress }; : state; } }; newsReducer; import from 'src/constants/store/actionTypes' const cachedNews lastUpdate 0 fetchInProgress false publishInProgress false updateInProgress false deleteInProgress inProgress false newsId '' refreshing false error null const ( ) => state = initialState, { type, payload } switch case return refreshing fetchInProgress true error null case return cachedNews lastUpdate refreshing false fetchInProgress false error null case return refreshing false fetchInProgress false error case return publishInProgress true case return cachedNews lastUpdate publishInProgress false case return publishInProgress false case return updateInProgress true case return cachedNews lastUpdate updateInProgress false case return updateInProgress false case return deleteInProgress inProgress true newsId case return cachedNews lastUpdate deleteInProgress case return deleteInProgress default return export default The size of the reducer has grown, and readability is reduced because of unnecessary code which doesn’t belong to newsReducer. This scenario would likely repeat itself in some other reducer and we would end up repeating code ( ). !DRY Let’s separate UI logic from reducers and create uiActions, uiReducer Our ui action creators will look like: { uiActionTypes } ; startAction = ({ : uiActionTypes.START_ACTION, : { : { name, params } } }); stopAction = ({ : uiActionTypes.STOP_ACTION, : { name } }); refreshActionStart = ({ : uiActionTypes.REFRESH_ACTION_START, : { refreshAction } }); refreshActionStop = ({ : uiActionTypes.REFRESH_ACTION_STOP, : { refreshAction } }); import from 'src/constants/store/actionTypes' export const ( ) => name, params type payload action export const => name type payload export const => refreshAction type payload export const => refreshAction type payload Our uiReducer will look like: { uiActionTpyes } ; initialState = { : { : [], : [] } }; uiReducer = { { loader } = state; { actions, refreshing } = loader; (type) { uiActionTpyes.START_ACTION: { ...state, : { ...loader, : [...actions, payload.action] } }; uiActionTpyes.STOP_ACTION: { ...state, : { ...loader, : actions.filter( action.name !== payload.name) } }; uiActionTpyes.REFRESH_ACTION_START: { ...state, : { ...loader, : [...refreshing, payload.refreshAction] } }; uiActionTpyes.REFRESH_ACTION_STOP: { ...state, : { ...loader, : refreshing.filter( refresh !== payload.refreshAction) } }; : state; } }; uiReducer; import from 'src/constants/store/actionTypes' const loader actions refreshing const ( ) => state = initialState, { type, payload } const const switch case return loader actions case return loader actions => action case return loader refreshing case return loader refreshing => refresh default return export default Our newsSaga will look like: { call, put, select, takeLeading } ; { deleteNewsSuccess, fetchNewsError, fetchNewsSuccess, publishNewsSuccess, updateNewsSuccess } ; { ApiService } ; { newsActionTypes } ; { startAction, stopAction, refreshActionStart, refreshActionStop } ; { { { refreshing } = payload; put(refreshing ? refreshActionStart(type) : startAction(type)); response = call(ApiService.getNews); put(fetchNewsSuccess(response)); } } (error) { .log( ,error); } { put(payload.refreshing ? refreshActionStop(type) : stopAction(type)); } } { takeLeading(newsActionTypes.FETCH_NEWS, fetchNewsSaga); } { { put(startAction(type)); { newsData } = payload; response = call(ApiService.publishNews, newsData); put(publishNewsSuccess(response)); } (error) { .log( , error); } { put(stopAction(type)); } } { takeLeading(newsActionTypes.PUBLISH_NEWS, publishNewsSaga); } { { put(startAction(type)); { newsId, newsData } = payload; call(ApiService.updateNews, newsId, newsData); response = call(ApiService.getNews); put(updateNewsSuccess(response)); } (error) { .log( , error); } { put(stopAction(type)); } } { takeLeading(newsActionTypes.UPDATE_NEWS, updateNewsSaga); } { { { newsId } = payload; put(startAction(type, { newsId })); call(ApiService.deleteNews, newsId); response = call(ApiService.getNews); put(deleteNewsSuccess(response)); } (error) { .log( , error); } { put(stopAction(type)); } } { takeLeading(newsActionTypes.DELETE_NEWS, deleteNewsSaga); } import from 'redux-saga/effects' import from 'src/store/actions/newsActions' import from 'src/services' import from 'src/constants/store/actionTypes' import from 'src/store/actions/uiActions' export * ( ) function fetchNewsSaga { type, payload } try const yield const yield yield catch console 'fetchNewsSaga error' finally yield export * ( ) function watchFetchNewsSaga yield export * ( ) function publishNewsSaga { type, payload } try yield const const yield yield catch console 'publishNewsSaga error' finally yield export * ( ) function watchPublishNewsSaga yield export * ( ) function updateNewsSaga { type, payload } try yield const yield const yield yield catch console 'updateNewsSaga error' finally yield export * ( ) function watchUpdateNewsSaga yield export * ( ) function deleteNewsSaga { type, payload } try const yield yield const yield yield catch console 'updateNewsSaga error' finally yield export * ( ) function watchDeleteNewsSaga yield Our newsReducer will look like: { newsActionTypes } ; initialState = { : {}, : , : }; newsReducer = { (type) { newsActionTypes.FETCH_NEWS_SUCCESS: newsActionTypes.PUBLISH_NEWS_SUCCESS: newsActionTypes.DELETE_NEWS_SUCCESS: newsActionTypes.UPDATE_NEWS_SUCCESS: { ...state, : payload.news, : payload.lastUpdate, : }; newsActionTypes.FETCH_NEWS_ERROR: { ...state, : payload.error }; : state; } }; newsReducer; import from 'src/constants/store/actionTypes' const cachedNews lastUpdate 0 error null const ( ) => state = initialState, { type, payload } switch case case case case return cachedNews lastUpdate error null case return error default return export default Look how much newsReducer is cleaner, after we have removed the UI part from it. Now it’s doing what it was supposed to do and that’s managing news related state. And final but not least our selectors: checkIfLoading = store.ui.loader.actions.some( actionsToCheck.includes(action.name)); checkIfRefreshing = store.ui.loader.refreshing.some( action === actionToCheck); getDeletingNewsId = { newsId = ; ( i = ; i < store.ui.loader.actions.length; i++) { action = store.ui.loader.actions[i]; (action.name === actionToCheck) { newsId = action.params.newsId; ; } } newsId; }; export const ( ) => store, ...actionsToCheck => action export const ( ) => store, actionToCheck => action export const ( ) => store, actionToCheck let undefined for let 0 const if break return Our NewsComponent has to just call the selectors to check which part is currently loading. { deleteNews, fetchNews } ; { checkIfLoading, checkIfRefreshing, getDeletingNewsId, getUserData } ; NewsComponent = { { news, isLoading, refreshing, deletingNewsId } = props; { cachedNews, error } = news; useEffect( { props.fetchNews(); }, []); deleteNews = { props.deleteNews(newsId); }; onRefresh = { refreshing = ; props.fetchNews(refreshing); }; (isLoading && !refreshing) { <ScrollView refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} > <NewsList {...{ cachedNews, deleteNews, deletingNewsId }} /> </ScrollView> ); }; const mapStateToProps = state => ({ news: state.news, deletingNewsId: getDeletingNewsId(state, newsActionTypes.DELETE_NEWS), isLoading: checkIfLoading(state, newsActionTypes.FETCH_NEWS), refreshing: checkIfRefreshing(state, newsActionTypes.FETCH_NEWS) }); const mapDispatchToProps = { fetchNews, deleteNews }; export default connect( mapStateToProps, mapDispatchToProps )(NewsComponent); import from 'src/store/actions/newsActions' import from 'src/store/selectors' const => props const const => () const => newsId const => () const true if return ; } return ( < = /> Loader text {locales.loadingNews} What about displaying a loading indicator on a specific news in the list? We are passing the which we get from the selector, to the function which renders the delete button and if the matches the id of the rendered news, instead of the delete button, a loading indicator is rendered. deletingNewsId, getDeletingNewsId deletingNewsId { (deletingNewsId === newsId) { <CustomButton text={locales.delete} onPress={() => deleteNewsAlert(newsId)} /> ); } ( ) function renderDeleteButton deletingNewsId, deleteNewsAlert, newsId if return ; } return ( < /> ActivityIndicator In PublishNews component we show the loading indicator if news are published or updated. Again we will use the selector for that, but this time we are passing multiple actionTypes to it. checkIfLoading { newsActionTypes } ; { checkIfLoading } ; { isLoading? : ; } PublishNews = { { isLoading } = props; ( <TextInput/> } mapStateToProps = ({ : checkIfLoading(state, newsActionTypes.PUBLISH_NEWS, newsActionTypes.UPDATE_NEWS) }); import from 'src/constants/store/actionTypes' import from 'src/store/selectors' ( ) function renderButton isLoading return < /> ActivityIndicator < /> Button const => props const return < > View {renderButton(isLoading)} </ > View const => state isLoading Summing up No approach is perfect so let’s address some of the downsides. Everytime we want to start and stop a loading action we have to do it manually. This introduces boilerplate code in our redux-sagas. At the beginning of the saga, we start the loading action and before the saga finishes we are stoping it, but this is needed to have maximum flexibility, i.e. deciding when the action starts and stops. Manually starting and stoping loading actions could be prevented if we would refactor our uiReducer like this: uiReducer = { { type } = action; matches = .exec(type); (!matches) { state; } [requestName, requestPrefix, requestState] = matches; { ...state, [requestPrefix]: requestState === }; }; uiReducer; const ( ) => state = {}, action const const /(.*)_(REQUEST|SUCCESS|ERROR)/ // not a *_REQUEST / *_SUCCESS / *_FAILURE actions, so we ignore them if return const return // Store whether a request is happening at the moment or not // e.g. will be true when receiving FETCH_NEWS_REQUEST // and false when receiving FETCH_NEWS_SUCCESS / FETCH_NEWS_ERROR 'REQUEST' export default Now every action type that ends with is added to actions object and set to true. The action type is set to false on every or action type, that matches the previous action type name. If you go with this approach you will need to cover edge cases like REFRESHING news (should go into separate object) and DELETING news (the id of the news that is being deleted needs to be stored). _REQUEST _SUCCESS _ERROR That’s still possible, but your uiReducer would become messy and you will the action start and stop, because it is handled automatically in uiReducer. Maybe you don’t want to start a loading action until some conditions aren’t met, again you can’t control that using this approach. That’s why I prefer the manually way of starting loading actions. wouldn’t be able to decide when exactly don’t put all loading logic into redux blindly, only if the data you are fetching is stored into redux , then the loading logic should be handled in redux-saga,thunk etc. If the data is stored in component state, then the loading flags should also be stored in component state. One example is an autocomplete search component. It should not use redux for storing loading flags, instead it should use component state. NOTE: Feel free to leave your comments, questions, suggestions 😃.