3

Sorry for the kind of vague title. The best way to explain my question might be an example.

I have a of items in redux, and the list is displayed in a react component using standard react-redux connected components. Each individual item has a button, which when clicked, does some asynchronous work, and then removes the item from the list and puts it in another list displayed somewhere else. It's important that the logic for starting the asynchronous work be handled in redux because it's important to the state of my application.

That basic functionality works, but now I want to add feedback to the button so that when the side effect succeeds, it changes the label to a Checkmark (for simplicity, i'll do nothing and leave the list unchanged if the request fails in this example). The item will stick around for an extra second with the checkmark before being removed from the list.

The problem is that if i remove the item from the list as soon as the async work is done, it is immediately unmounted, so I need to delay that. I've been trying to come up with a way to implement this logic that is reusable across my app, as I'll want the checkmark feedback in other unrelated parts of the app.

The simple solution is to dispatch an action on success that just changes the state to indicate that the item's request succeeded, and then do a setTimeout to dispatch another action 1 second later to actually remove the item from the list.

I feel like doing that logic will become very repetitive if i do it in different places across my app where I have a button. I'd like to be able to not have to repeat the timeout logic for every new button that needs this. But I want what my app displays to represent the current state of my app.

Has anyone dealt with an issue like this before?

Thanks

Edit: I don't think it should really change the general solution, but I'm using redux-loop to handle side effects. I feel like a generic solution will work fine with thunk or saga or whatever else though.

bdwain
  • 1,665
  • 16
  • 35
  • If you haven't already, check out: http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559 – Derek Mar 17 '17 at 02:16
  • Yep I've seen it. My question is more of an architecture question of how to do this repeatedly in a clean way without repeating the logic everywhere. For example, in item reducer, i have logic to store which buttons are in which states. But i want to avoid rewriting that kind of logic in a separate reducer that deals with a different type of object. One thing I thought of is maybe having a generic buttonState reducer that handles the states for all of the buttons across the app. Something like that. But i was interested to see if people had solutions that worked first. – bdwain Mar 17 '17 at 03:08

2 Answers2

5

You mentioned that you are using redux-loop to handle your async stuff. I'm more familiar with redux-thunk, so if it's ok with you, I'll give you an answer that uses a thunk.

You can keep your code DRY if you put the timeout in your action creator, and then call that action creator from multiple buttons:

// actionCreators.js

const fetchTheThings = (url, successAction, failureAction, followUpAction) => (dispatch) => {

  //if you put the app in an intermediate state
  //while you wait for async, then do that here
  dispatch({ type: 'GETTING_THINGS' });

  //go do the async thing
  fetch(url)
    .then(res => {
      if (res.status === 200) return res.json();
      return Promise.reject(res);
    })
    .then(data => {
      //on success, dispatch an action, the type of which
      //you passed in as an argument:
      dispatch({ type: successAction, data });

      //then set your timeout and dispatch your follow-up action 1s later
      setTimeout(() => {
        dispatch({ type: followUpAction });
      }, 1000);
    })
    .catch(err => {
      //...and handle error cases
      dispatch({ type: failureAction, data: err });
    });
};

//then you can have exported action creators that your various buttons call
//which specify the action types that get passed into fetchTheThings()

export const actionFiredWhenButtonOneIsPressed = () => {
  return fetchTheThings('<some url>', '<success action>', '<failure action>', '<follow-up action>');
};

export const actionFiredWhenButtonTwoIsPressed = () => {
  return fetchTheThings('<other url>', '<other success action>', '<other failure action>', '<other follow-up action>');
};

Hopefully that at least gives you some ideas. Good luck!

Ian
  • 3,806
  • 2
  • 20
  • 23
  • thanks for your help. I implemented something similar to what you described. I think ideally, react would allow a component to delay its own unmounting, and that would simplify this because it would allow me to keep the state updates in reudx and have only the button worry about this delayed unmounting logic. But that is not implemented, so this is the best option for now. – bdwain Mar 20 '17 at 04:59
1

Ian's solution should be generalizable pretty well, but maybe, if you can live with a success confirmation that doesn't require DOM activity:

A simple unmount style that turns the element green and then fades it out, would be sufficient for a satisfying user feedback to tell that stuff has worked out.

flq
  • 22,247
  • 8
  • 55
  • 77