SKeney
SKeney

Reputation: 2301

Redux Saga Blocking

I'm new to Redux Saga and generators in general so am trying to wrap my head around how to "await" a dispatched action before executing the rest of code after the dispatch.

For example:

    // Sideeffect to load messages on mount
    useEffect(() => {
        let mounted = true;

        if(mounted) {
            dispatch({ type: 'GET_THREAD_MESSAGES', threadId: _id })
            setIsLoading(false);
        }

        return () => {
            dispatch({ type: 'CLEAR_FOCUSSED_MESSAGES' })
        }
    }, [])

When the component first mounts it will dispatch GET_THREAD_MESSAGES. I watch for said action in my rootSaga with the following:

export default function* rootSaga() {
    yield all([
        ...
        watchGetThreadMessages(),
        watchClearFocussedMessages()
    ])
}

My applicable sagas:

export function* watchGetThreadMessages() {
    yield takeEvery('GET_THREAD_MESSAGES', getThreadMessages)
}

export function* getThreadMessages({ threadId, lastDate }) {
    const { token } = yield select(state => state.auth);

    try {
        const { data } = yield call(getMessages, token, threadId)

        yield put({
            type: 'GET_THREAD_MESSAGES_SUCCESS',
            allMessages: data.messages,
            totalMessages: data.count
        })

        console.log('retrieved messages')
    } catch(err) {
        console.log(err)
    }
}

getMessages is an axios call to my endpoint getting data from the server. The issue is that my setIsLoading after the component mount dispatch will fire before I am done fetching my messages from the axios call as it is asynchronous. Normally if I was fetching the data inside the component I would just await this response but am unsure how to await a dispatch action. I've gotten around it by passing my setIsLoading to the dispatch and firing the setIsLoading inside the saga but that isn't the most elegant solution and would like to know a better process if there is one.

Any thoughts or suggestions? Any are much appreciated.

Upvotes: 1

Views: 616

Answers (1)

oozywaters
oozywaters

Reputation: 1241

Since you actions are just plain synchronous functions, there is no way to track isLoading state inside a component. You have to keep isLoading in redux store instead, and connect to your component. Whenever GET_THREAD_MESSAGES_SUCCESS action is dispatched, reducer will change isLoading value to false. For example:

import React, { useEffect } from 'react';
import { connect } from 'react-redux';

// isLoading prop is mapped to state.messages.isLoading
function YourComponent({ isLoading, dispatch }) {
  useEffect(() => {
    dispatch({ type: 'GET_THREAD_MESSAGES', threadId: 'someId' });
  }, []);

  if(isLoading) {
    return <div>Some loader</div>;
  }

  return <div>Your component</div>;
}

function mapStateToProps(state) {
  return {
    isLoading: state.messages.isLoading,
  };
}

export default connect(mapStateToProps)(YourComponent);

Your messages reducer may look like the following:

export default function messagesReducer(state, action) {
  switch (action.type) {
    case 'GET_THREAD_MESSAGES':
      return {
        ...state,
        isLoading: true,
      };
    case 'GET_THREAD_MESSAGES_SUCCESS':
      return {
        ...state,
        isLoading: false,
        allMessages: action.allMessages,
        totalMessages: action.totalMessages,
      };
    default:
      return state;
  }
}

Upvotes: 2

Related Questions