Idea - "Change State" Effect
See original GitHub issueHi @yelouafi,
I’d like to preset an idea - if it’ll sound good to you then I’ll write it and add it as a pull request. The idea is to have a ‘change state’ effect, syntax as follows:
yield(changeState((state) => {
const newState = {...state, x:1, y:2};
return newState;
});
This effect allows saga to directly change the state (and not go through reducers).
Why is this good?
In our usage of sagas we do all logic (sync and async) in the sagas. At the end of each saga, the saga “puts” an action handled by the reducers that only updates the state to the value given. It makes the code, flows, and sagas much cleared - All business logics are centered in the sagas and sagas only. It makes it very clear to see flows, and to even test them (see my previous issue).
Basically, using this design, reducers have become unneeded ‘dead weight’. Having this effect will remove the need for reducers (which, again, in our case do almost nothing).
How would we use it?
Our system is very complex - it’s an online ordering system, with its management dashboard used by hundreds of thousands of restaurants worldwide - including React Native apps that use the same sagas as the web. This design (all business logic in Sagas, no reducers whatsoever) made the system much easier to understand, develop, and test (we use TDD, or try to at least).
What do you think?
Issue Analytics
- State:
- Created 7 years ago
- Comments:6 (5 by maintainers)
Top GitHub Comments
Also it looks to me a terrible idea.
The initial philosophy of a saga is to listen to events, manage its own internal state, and eventually emit new events.
The saga, in the first place, should generally avoid access to redux state. The
select
effect has been done because it is actually sometimes convenient and can help reduce boilerplate, but it is absolutly optional and for me it’s not at all a best practice to rely onselect
heavily. The saga should be an autonomous component, that does not query other components of the system. Initially it comes from backend distributed systems. It is to consider that the saga is supposed to run on its own microservice, and should not communicate with other microservices with anything else than the event stream. It is generally advised, in distributed systems, that microservices do not access storage of one each others because it creates a lot of mess.This is less dramatic for Redux, but still the same concerns apply. By relying on redux state inside sagas, and not only actions, you actually couple your saga to the state shape you choose for your React application. A properly designed saga should not assume anything about how your UI state is handled or shaped, but should just do its job and emit new events when needed. Actually, the saga should not even have to know that you are using React, or Redux. If you do things properly decoupled, you could switch to backbone and reuse the exact same unmodified sagas.
It is also important to emit new events, because these events can be useful for other sagas, or listened to by other pieces of the system (for example an event-tracking system like Mixpanel/GoogleAnalytics/Splunk/GetSentry…). If you modify state in place, it is much harder, later, to introduce these kind of usages. For example, in my app, I recently displayed an error message to the user for every request failure that is not due to the network, as a technical error. If you don’t have the request informations as an event, you will have to pollute all your sagas doing api requests with this concern, while this behavior should more elegantly be plugged to the existing system by using a single-purpose technical error notifier saga.
The saga somehow express “if This, then That”. If you never emit an event for That, but only changes the state directly, nobody in the future will have the ability to plug behavior on the That event, that is never emitted.
You will also loose some ability for hot code reloading. By changing the state directly in the saga, it’s not possible anymore to reinterpret your action log by running it again against an hot-reloaded updated reducer. Redux-saga in its current form will not help you much and it’s quite hard to hot reload generators.
@slorber - Thanks for your very detailed answer.
Most of apps contain different business logic flows. For example: ‘When a user wants to checkout, show him a contact form, wait for him to click next, then show him a delivery form, …’, or for example: ‘when a user wants to delete an item, show him a are-you-sure popup, wait for him to click yes or no, if he/she clicked no, close the popup. if they clicked yes, show a thinker, send the server request, and return back’. Sagas lets us write these flows very explicitly clear:
Up until here, I believe we both agree in the usage of sagas as a descriptive way to write your business logic. It’s a very powerful tool, that can easily be read and tested. (correct me if I’m mistaken and you disagree).
However, here’s where I personally am willing to “pay” in all the advantages you’ve mentioned, in order to achieve something else: readability. Instead of throwing actions that change the state using reducers (even the generic reducer you suggested), I want to change the state in the saga. And furthermore, in some sagas i DO want to access the state (which tightly couples the saga to Redux state).
Why? Because I feel that the advantage of having your entire business logic flow concentrated in one place makes development, debugging, and refactoring of business logic ten times easier. To be honest, I believe the ‘payment’ in complexity I’m paying navigating between different files and flows due to logic split between reducers and sagas accumulates to much more (in terms of developer hours) than the payment I would pay if I ever want to refactor my code from Redux to Backbone (I’ll probably do that once every 2-3 years, compared to debugging I do all the time).
So to comment specifically on things you said:
Again, thanks for your comments.