Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Action-Creators and Reducers: Business Logic #602

Closed
raineorshine opened this issue Apr 28, 2020 · 7 comments
Closed

Action-Creators and Reducers: Business Logic #602

raineorshine opened this issue Apr 28, 2020 · 7 comments
Labels
refactor Refactor without changing behavior

Comments

@raineorshine
Copy link
Contributor

raineorshine commented Apr 28, 2020

Should we have more business logic in action-creators or reducers?

This is mostly a response to “Recommendations for best practices regarding action-creators, reducers, and selectors”:
reduxjs/redux#1171.

This thread is 5 years old, so a newer source should probably be consulted before making a final conclusion. Nevertheless I read the whole thread and the consensus seemed to be that action-reducers should be small, simply representing the user or browser action, and the logic should go into the reducers. The original post that recommended fat action-creators was speculation from someone who had been using Redux for only a couple months.

Reducers are easier to test and compose since they are pure functions. Plus you get the benefit of things like hot reloading when the logic is in reducers. There was a fair amount of pragmatism represented in true Redux fashion, but the recommend approach was in favor of logic in reducers.

Here’s my evidence:

Dan Abramov:
reduxjs/redux#1171 (comment)

Imperative vs Reactive: reduxjs/redux#1171 (comment)

“Action creators are just a pattern for organizing the code. It’s convenient to have factories for actions because you want to make sure that actions of the same type have consistent structure, have the same side effect before they are dispatched, etc. But from Redux point of view, action creators don’t exist—Redux only sees actions.”

Hot reloading benefit: reduxjs/redux#1171 (comment)

A comment Dan made also suggested that composing reducers is generally a good thing: reduxjs/redux#1171 (comment)
“For reducers, we also don’t recommend using classes because it will be harder to use reducer composition, that is, reducers that call other reducers.” [emphasis mine]

More:
reduxjs/redux#1171 (comment)
reduxjs/redux#1171 (comment)

Other considerations:

  • By auto calling the reducer with the same name as the action, it appears I’m following the ducks approach of co-locating actions and reducers. This is not something that Dan Abramov supports. I like the simplicity of it, but I will have to pay attention to how it couples actions to state and keep an eye out for potential cases where it would be more elegant to have multiple reducers respond to a single action.
  • There was some support expressed for the idea of handling side effects in middleware. I’d really like to do that to get sync out of the reducers, although I’m also consider wrapping a reducer so that it can be called just with the state delta (change in state).
  • redux-saga would certainly make our current thunk-based action-creators more declarative, but I admit I really don’t care for the generator syntax. I am interested in what FRP alternatives are out there. This comment in the same thread hints at this: Recommendations for best practices regarding action-creators, reducers, and selectors reduxjs/redux#1171 (comment). Not a lot of options out there from what I can tell. redux-cycles is no longer maintained, but that’s totally the right idea. refract looks pretty good and is still maintained, though not actively.

@shresthabijay @anmolarora1 @aqkhan Would love to hear any thoughts you have on any of this.

@raineorshine raineorshine added the refactor Refactor without changing behavior label Apr 28, 2020
@anmolarora1
Copy link
Collaborator

anmolarora1 commented May 5, 2020

That's quite a bit of information 😄
Though there are plenty of good options here, I don't think that there's a single best way of doing things because of the nature of the problem.
In my opinion, here are a few solutions that might be suitable (sorted by preferences) -

  1. The idea of co-locating the action-creators and reducers doesn't seem very scalable if we're trying to move away from the imperative style of doing things. To start with the solution, we can move the side-effect handling logic to reducers and follow this approach, while we work towards building an architecture like this
  2. Using Redux-Saga seems like a good option, and resonates well with our goals. Also, it comes up with some good declarative effects that can be plugged in and used right away.
    This involves a bit of a learning curve to get familiarized with the yield pattern.
  3. The idea of using RxJS or Refract seems pretty interesting if we truly want to move to FRP.
    Because our codebase is still evolving and many sections are quite tangled, a progressive transition would be quite challenging. Plus, the paradigm shift will take a considerable amount of time.

@raineorshine
Copy link
Contributor Author

That's quite a bit of information 😄
Though there are plenty of good options here, I don't think that there's a single best way of doing things because of the nature of the problem.

Thanks for doing some research!

  1. The idea of co-locating the action-creators and reducers doesn't seem very scalable if we're trying to move away from the imperative style of doing things. To start with the solution, we can move the side-effect handling logic to reducers and follow this approach, while we work towards building an architecture like this

Yup, I agree.

  1. Using Redux-Saga seems like a good option, and resonates well with our goals. Also, it comes up with some good declarative effects that can be plugged in and used right away.
    This involves a bit of a learning curve to get familiarized with the yield pattern.

Yes, it is in alignment with being more functional and declarative. I need to spend more time with the generator pattern and see if I don't like it because it's unfamiliar or because my intuition is right and there are better ways. I think that's why I was attracted to some of the frp solutions.

  1. The idea of using RxJS or Refract seems pretty interesting if we truly want to move to FRP.
    Because our codebase is still evolving and many sections are quite tangled, a progressive transition would be quite challenging. Plus, the paradigm shift will take a considerable amount of time.

Do you think Redux-Saga is less of a paradigm shift than Refract?

@anmolarora1 I didn't see an explicit comment about fat reducers. Do you think that decoupling side effects and moving as much logic to reducers as possible is a reasonable approach? That's where I'm leaning currently.

@shresthabijay
Copy link
Collaborator

@raineorshine My thoughts on the topic -

  1. The current implementation of Redux in the application is in a way that multiple reducers can change same piece of state. For example setCursor reducer modifies cursor, tutorial , thoughtsRanked and many more. It even use another settings reducer logic depending on the specific condition of tutorial. Also cursor is modified by many other reducers. So if we ask how and where is the cursor state being updated in the application, then we have to look at every reducer to check if it is updating the cursor. So my first concern with the current architecture is predictability. We cannot predict which specific reducers will change specific part of the state. Generally it is suggested to group specific slices of state to specific reducers. These reducers will have access to all current store state and but will only modify parts of state that they are responsible for. But looking at complex state and tight relationship between multiple parts of the state , the challenge still remains how do we decouple the responsibility of specific parts to state to specific reducers and orchestrate the flow with ease.

  2. I think FRP is quite interesting specially Refract. But redux-saga is well documented and actively maintained. So it really depends on which one has less of a paradigm shift.

@raineorshine
Copy link
Contributor Author

raineorshine commented May 11, 2020

@raineorshine My thoughts on the topic -

  1. The current implementation of Redux in the application is in a way that multiple reducers can change same piece of state. For example setCursor reducer modifies cursor, tutorial , thoughtsRanked and many more. It even use another settings reducer logic depending on the specific condition of tutorial. Also cursor is modified by many other reducers. So if we ask how and where is the cursor state being updated in the application, then we have to look at every reducer to check if it is updating the cursor. So my first concern with the current architecture is predictability. We cannot predict which specific reducers will change specific part of the state. Generally it is suggested to group specific slices of state to specific reducers. These reducers will have access to all current store state and but will only modify parts of state that they are responsible for. But looking at complex state and tight relationship between multiple parts of the state , the challenge still remains how do we decouple the responsibility of specific parts to state to specific reducers and orchestrate the flow with ease.

Yes, this makes a lot of sense. It leaves me wondering:

  1. Is our data model amenable to isolating reducers to specific slices of the state?
  2. In your view, how do we "decouple the responsibility of specific parts to state to specific reducers and orchestrate the flow with ease"?

In a typical web app, I could imagine different slices of the state corresponding to different domains of the app: Customers, Orders, Account Information, etc. 90% of em is effectively one component: the editor and its corresponding thoughts. Because of this, I would venture to guess that em has higher than typical cross-state dependencies. In this case, leveraging more reducer composition will yield more gains than trying to partition the state. I can only speculate though, as I have not built other React apps.

There are parts of the state like the tutorial that should be more decoupled. Maybe there are some slices of the state that can be handled more independently.

  1. I think FRP is quite interesting specially Refract. But redux-saga is well documented and actively maintained. So it really depends on which one has less of a paradigm shift.

Yes, an open question.

@anmolarora1
Copy link
Collaborator

anmolarora1 commented May 12, 2020

@raineorshine I'm not sure about having fat reducers, perhaps because of my aversion towards fat functions in general. But we don't want fat action-creators.
Composition of reducers and making use of utility functions seems like a good approach because that'll help us handle all business logic within the scope of reducers. Here's a simple example from the official redux docs.
I agree with you about the architectural complexity of em being more than a traditional react-redux application. But the current code structure makes it look more intimidating than it is. However, we're already seeing some big improvements in the codebase and I'm pretty positive that we have the same with actions/reducers if we start with one step at a time.
Moving all logic from action-creators to reducers can be the first step. And then, we can split the reducers, have util functions, and make use of the composition as we proceed.

@raineorshine
Copy link
Contributor Author

I'm not sure about having fat reducers, perhaps because of my aversion towards fat functions in general. But we don't want fat action-creators.

I'm more "anti fat action-creators" than "pro fat reducers" too 😁

Moving all logic from action-creators to reducers can be the first step. And then, we can split the reducers, have util functions, and make use of the composition as we proceed.

That sounds like a good plan: iterative and purposeful.

@raineorshine
Copy link
Contributor Author

Almost all synchronous, pure action-creators have been converted to reducers in #706.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Refactor without changing behavior
Projects
None yet
Development

No branches or pull requests

3 participants