question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Idea - "Change State" Effect

See original GitHub issue

Hi @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:closed
  • Created 7 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

7reactions
slorbercommented, Sep 15, 2016

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 on select 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.

1reaction
3LOKcommented, Sep 15, 2016

@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:

    function* handleOnDelete(dep, action) {
        yield put(actions.showAreYouSurePopup());
        const result = yield take([types.DELETE_POPUP.YES, types.DELETE_POPUP.NO]);
        yield put(actions.hideAreYouSurePopup());
        if (result.type === types.DELETE_POPUP.YES) {
            yield put(actions.showThinking());
            yield call(dep.server.deleteItem, action.itemId);
            yield put(actions.stopThinking());
        }
    }

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:

  1. Saga not accessing other storage - I agree, but I view it differently. For me there’s the constant pairing of <Saga, Reducer>, and I feel that each pair should have a single ‘database’ (the specific path in the store). I don’t believe pairs should access other pairs ‘storage’, but I think in the pair it should be perfectly valid and logical to have the saga access the store it manipulates.
  2. Coupling sagas and redux states - again, I’m willing to pay in this coupling to have clearer and easier code to follow and debug. I don’t really think we will move away from redux without also changing the sagas on one hand, and at the same time I’m willing to let “Yoav of 2018” pay the price for this decision so “Yoav of 2016” will have an easier time following the flow of the system.
  3. Events for other sagas/plugins - I agree - I’ll be honest that this point is making me reconsider my solution. Let me think about this 😃 I might design a special structure of events that you can explicitly define how they need to change the state.
  4. Hot reloading - I think when you move to redux-saga you generally release any hot-reloading of your business logic, and focus your hot reloading only on your React layer. I don’t think my structure makes hot reloading less feasible on the “Flux” level then it is right now.

Again, thanks for your comments.

Read more comments on GitHub >

github_iconTop Results From Across the Web

About IDEA - Individuals with Disabilities Education Act
Congress reauthorized the IDEA in 2004 and most recently amended the IDEA through Public Law 114-95, the Every Student Succeeds Act, in December...
Read more >
Alter the program's execution flow | IntelliJ IDEA Documentation
In the properties of the new breakpoint, clear the Suspend checkbox and enter the expression that will modify the program state in the...
Read more >
What is the Individuals with Disabilities Education Act? | DO-IT
In 1990, amendments to the law were passed, effectively changing the name to IDEA. In 1997 and again in 2004, additional amendments were ......
Read more >
Observer effect (physics) - Wikipedia
Language · Watch · Edit. For other uses, see Observer effect. In physics, the observer effect is the disturbance of an observed system...
Read more >
Taming state and side effects on Android | by Ivan Kupalov
3) State object is immutable: when your state changes, you have to create new state object. How do you describe state changes? As...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found