Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Orchestrating multiple actions

It’s unclear to me how to coordinate multiple, sequential actions in Redux. For example, I’ve got an app that has a summary panel on the left and a CRUD panel on the right. After the user CRUDs, I want the app to automatically update the summary. I also want the ability to refresh the summary and CRUD independently of each other as well. In other words, I don’t want the CRUD actions to have to know about the summary or any other future downstream follow-on actions (or vice versa). Is there a best practice to coordinate this without having my action creators know about other actions?

For example, here’s the (thunk) action creator for clearing a collection of entities (a CRUD action). Right, now it tightly couples the fetchSummary() dispatch.

export function clearCollection(collection) {
    return function(dispatch) {
        dispatch(requestCollectionClear(collection));

        return doClearCollection(collection)
            .then(function(coll) { 
                dispatch(receiveCollectionCleared(coll))
            })
            .then(function() {
                dispatch(fetchSummary()); // <-- How do I chain actions without tight coupling?
            });
            // TODO: .catch()
    }
}

where requestCollectionClear() kicks off the async action and fetchSummary() is the action creator for the subsequent step in the workflow. What’s the best pattern to separate fetchSummary() from clearCollection(), decoupling them for use independent of each other?

like image 617
Justin Makeig Avatar asked Oct 31 '22 11:10

Justin Makeig


2 Answers

If I'm understanding your answer to my question in the comment correctly (that your CRUD ops update some collection data and your summary displays some version of the same collection data), I think you can obviate your problem by thinking about your application differently.

Your state should be something along the lines of an object with a collections key and your reducers should operate on this object. So a clearCollections method would set the collections value to an empty array, a fetchCollections method would set the collections value to a fresh array of data from the server, etc.

Then, your summary would just subscribe to your store and display the latest and greatest value for the collections key. No more tight coupling.

EDIT: Based on the comments I think you are basically asking how to coordinate between your server and client with regard to your data. One way of handling this is to fetch your collection data from the server on app load, then populate your Redux store with this data by dispatching an action (and passing your data to the action creator):

// Fetch your data
$.ajax({
  url: '/collections',
  method: 'GET',
  success: function(response) {
    // Populate your Redux store by dispatching an action
    store.dispatch(setCollections(response));
  }
});

Then, when you want to update your data, say add a collection, you would do something like this:

// Update the Redux store, which updates the UI.
store.dispatch(addCollection(someNewData));

Then update the server, and reconcile on response

$.ajax({
  url: '/collections',
  method: 'POST',
  data: {collection: someCollection},
  success: function(response) {
    // Should be a no-op b/c client has already
    // been updated.
    store.dispatch(setCollections(response))
  }
});

Your action creators and reducers would look like this:

// Action creators:
function setCollections(collections) {
  return {type: 'setCollections', collections: collections}
}

function addCollection(collection) {
  return {type: 'addCollection', collection: collection}
}

// Reducer
function reduceCollections(state = initialState, action) {
  if (action.type === 'setCollections') {
    return {collections: action.collections}

  } else if (action.type === 'addCollection') {
    return {
      collections: state.collections.concat([action.collection])
    }

  } else {
    return state
  }
}

How you handle all this is up to you. I've tried to keep it simple above to illustrate the point but you could use thunks for the async calls and switch statements or object maps for the reducers if you prefer.

like image 121
Mark McKelvy Avatar answered Nov 09 '22 15:11

Mark McKelvy


Approaching this with fresh eyes and more experience after three years, I’d definitely externalize the state transitions, using something like XState. The state machine would keep track of the transitions and Redux would maintain the backing data, dispatching update events to components.

like image 30
Justin Makeig Avatar answered Nov 09 '22 14:11

Justin Makeig