Let's say I have a REST API to edit a user profile. There are two methods concerned here
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:
udpateProfileAction
is suddenly responsible for also fetching the completion);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.
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$
}
}
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.
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.