Alex Florin
Alex Florin

Reputation: 431

What is the best approach of writing redux actions that need data from other actions

I have made some research about possible ways to do it, but I can't find one that uses the same architecture like the one in the app I'm working on. For instance, React docs say that we should have a method which makes the HTTP request and then calls actions in different points (when request starts, when response is received, etc). But we have another approach. We use an action which makes the HTTP call and then dispatches the result. To be more precise, my use case is this:

// action to get resource A
getResourceA () {
  return dispatch => {
    const result = await axios.get('someLink');
    dispatch({
      type: GET_RES_A,
      payload: result
    });
  }; 
}

// another action which needs data from resource A
getSomethingElseByIdFromA (aId) {
  return async dispatch => {
    const result = await axiosClient.get(`someLink/${aId}`);
    dispatch({
      type: GET_SOMETHING_BY_ID_FROM_A,
      payload: result
    });
  }; 
}

As stated, the second action needs data from the first one.

Now, I know of two ways of doing this:

  1. return the result from the first action
getResourceA () {
  return async dispatch => {
    const result = await axios.get('someLink');
    dispatch({
      type: GET_RES_A,
      payload: result
    });
    return result;
  }; 
}

// and then, when using it, inside a container
async foo () {
  const {
    // these two props are mapped to the getResourceA and
    // getSomethingElseByIdFromA actions
    dispatchGetResourceA,
    dispatchGetSomethingElseByIdFromA
  } = this.props;

  const aRes = await dispatchGetResourceA();
  // now aRes contains the resource from the server, but it has not
  // passed through the redux store yet. It's raw data
  dispatchGetSomethingElseByIdFromA(aRes.id);
}

However, the project I'm working on right now wants the data to go through the store first - in case it must be modified - and only after that, it can be used. This brought me to the 2nd way of doing things:

  1. make an "aggregate" service and use the getState method to access the state after the action is completed.
aggregateAction () {
  return await (dispatch, getState) => {
    await dispatch(getResourceA());
    const { aRes } = getState();
    dispatch(getSomethingElseByIdFromA(aRes.id));
  };
}

And afterward simply call this action in the container.

I am wondering if the second way is all right. I feel it's not nice to have things in the redux store just for the sake of accessing them throughout actions. If that's the case, what would be a better approach for this problem?

Upvotes: 0

Views: 70

Answers (2)

Yosif Mihaylov
Yosif Mihaylov

Reputation: 108

I think having/using an Epic from redux-observable would be the best fit for your use case. It would let the actions go throughout your reducers first (unlike the mentioned above approach) before handling them in the SAME logic. Also using a stream of actions will let you manipulate the data throughout its flow and you will not have to store things unnecessary. Reactive programming and the observable pattern itself has some great advantages when it comes to async operations, a better option then redux-thunk, sagas etc imo.

Upvotes: 1

dorriz
dorriz

Reputation: 2679

I would take a look at using custom midleware (https://redux.js.org/advanced/middleware). Using middleware can make this kind of thing easier to achieve.

Something like :

 import {GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR  } from '../actions/actionTypes'

    const actionTypes = [GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR ]

    export default ({dispatch, getState}) => {

        return next => action => {

            if (!action.type || !actionTypes.includes(action.type)) {

                return next(action)

            }

            if(action.type === GET_RESOURCE_A){
              try{
                // here you can getState() to look at current state object 
                // dispatch multiple actions like GET_RESOURCE_B and/ or 
                // GET_RESOURCE_A_SUCCESS
                // make other api calls etc....
               // you don't have to keep stuff in global state you don't 
               //want to you could have a varaiable here to do it
              }
             catch (e){

              }  dispatch({type:GET_RESOURCE_A_ERROR , payload: 'error'})
            }

        }

    }

Upvotes: 0

Related Questions