Search code examples
javascriptreactjsreduxredux-thunk

How to handle dependent action creators in Redux?


Let's say I have a REST API to edit a user profile. There are two methods concerned here

  • a method to update the user profile;
  • a method to get the current profile's completion score.

Of course, when the user updates his profile, I want to fetch the completion score.

Here is my (naive ?) way to do this with redux and redux-thunk action creators

function updateProfileAction(userId, profile) {

  return function (dispatch) {
    dispatch("PROFILE_UPDATE", /*...*/)
    api.updateProfile(userId, profile)
      .then(repsonse => dispatch("PROFILE_UPDATE_SUCCESS", /*...*/))
      // ▼ triggers the action that fetch the completion score
      // this is the line that bugs me
      .then(() => fetchProfileCompletionAction(userId))
  }

}

What bugs me with this line:

  • it breaks the single responsibility principle (the udpateProfileAction is suddenly responsible for also fetching the completion);
  • it is not fractal (if I want to add a feature that depends on the profile update, I need to modify, yet again, that piece of code.

For example if, for some reason, I don't want to display the completion anymore (say I remove the component that displays it) and forget to remove this line of code, the fetchCompletion will be triggered but will never be used in a graphical component).

I was thinking, maybe in the component that consumes the completion being fetched, I could subscribe to the store and dispatch at this moment. This would allow me to code in a more reactive way

function Completion(props) {

  props.store.subscribe(state => {
    //check if profile has changed
    dispatch(fetchProfileCompletionAction(userId))
  })

  return <div> {props.completion} </div>
}

Is this a good idea? Is there a better way to do this? Also I guess checking that the relevant state has changed is not really trivial with this solution.


Solution

  • So after a few tests an researches I tried redux-cycles as a replacement of redux-thunk which handles that kind of problems very nicely.

    It allowed me to subscribe to the profile being changed and request the completion against the server when the profile is updated. Which is way more semantically correct than what I was doing in the initial question.

    Here is what the code looks like

    function main(sources) {
      const updateProfileRequest = sources.ACTION
        .filter(action => action.type === "UPDATE_PROFILE")
        .map(action => ({ /* update profile request object */))
    
      const profileUpdateSuccess$ = sources.HTTP
        .select("profileUpdate")
        .flatten()
        // when a profile request is successful, I want to warn the store
        .map(response => ({ type: "UPDATE_PROFILE_SUCCESS", profile: response.body })
    
      const fetchCompletionRequest$ = sources.STATE
        .map(state => state.profile)
        // whenever the state changes, I want to fetch the completion
        .compose(dropRepeats())
        .mapTo({ /* fetch profile request object */ })
    
      return {
        HTTP: xs.merge(udpateProfileRequest$, fetchCompletionRequest$),
        ACTION: profileUpdateSuccess$
      }
    }
    

    The semantic with redux-thunk

    In my first attempt, the semantic of the code is:

    When the user request for an update of his profile: send the update request to the server AND when the response arrives dispatch the profile_update_success AND send the request to fetch the new completion value

    And that was the responsibility of a single component.

    The semantic with redux-cycles

    Now, with redux-cycles here is what my code means:

    When the user requests for an update of his profile: send the update request to the server

    When an update profile response arrives: send the UPDATE_PROFILE_SUCCESS action

    When the store's profile is updated: send the request to get the new completion

    Those three actions are completely decoupled and have an individual semantic.

    Notice how the second step is "When an update profile response arrives" and not "When the update profile response" :)

    So in the end, I am not solving the non fractal issue, but I think this is in the very nature of redux so there is not much I can do about this, but at least I have a much more reactive system.