Which should be simpler, actions or reducers?

Why not both?

Actions and Reducers should both be as simple as possible.

This is easier said than done, but by introducing other concepts and patterns such as selectors, sagas and even simple utility functions/classes, the complexity can be greatly reduced on both actions and reducers.

Actions

I know the term "broadcast" is really frowned upon in the flux world - but I find it helps me refine what should belong in my actions. The action should "broadcast" to the world what just happened - it doesn't personally care what is to follow, or how many reducers choose to respond to it - its just the messenger. ie: it does not care what the app looks like after said action.

My opinion is that business logic/rules either belong here directly, or can be channeled here via helper utility functions. See the sections below about async and utility.

It should describe what happened. Describe, describe, describe

Reducers

Reducers should form a full (ish) representation of your app with the minimal amount of data possible. Where possible, keep your state tree normalized to make sure you are holding minimal state.

My opinion is that these should be a light as possible, and at most should be just basic object manipulation - adding/removing keys or updating values.

What should the state look like based on the description I just received? (from the action)

Approach

It sounds crazy, but rubber duck debugging (in this case rubber duck programming) really helps for planning your redux structure.

I will literally speak (sometimes even outloud) through the steps like, for example:

  • You hover over the post title and click edit
  • The app swaps to the "edit post" page
  • You can edit the title field (and the field will update)
  • You can edit the body field (and the field will update)
  • The app will save every 90 seconds as a draft, a little save icon will appear in the upper right during the auto saves, but you can continue to work
  • You can also save by clicking the save button at anytime. During this time you won't be able to edit, and you will be redirected to the post index page

This can loosely be translated into actions and reducers, by looking at what is describing and what is, as a result, a change in state:

  • Click edit: Action - We are describing to the app that a post of id X has been clicked
  • Swaps to "edit post" page: Reducer - When we "hear" a "post edit" change the state of the app so its now presenting the post edit page
  • Edit title/body: Action - We describe which field and what value is entered by the user.
  • Update title/body: Reducer - State of the app is changed based on fields being entered into
  • Autosave: Action/reducer/action/reducer
    • Action: We describe to the app that an autosave has began
    • Reducer: State of app changes to show autosave in progress
    • Action: We describe autosave has completed
    • Reducer: State of app changes to hide autosave in progress
  • etc

The point I am trying to make, albeit long windily, is that its not what should be simpler, its where things (kind of) belong, but most likely you will see that your actions end up being more complex with your reducers less so.

But, my actions still aren't thin..

All of the above is easy enough to say, but how do you keep your those business-y actions clean/slim/light/simple/etc ?

At the end of the day, most business logic doesn't have a lot to do with React/Redux - so they can usually be shovelled out into their utility functions or classes. Which is great for a few reasons a) easier to test because it hasn't zero linkage to React/Redux and b) keeps your actions light, and c) more encapsulated - having minimal knowledge of react/redux is not a bad thing.

It's all just Javascript at the end of the day - there is nothing wrong with importing custom business classes or utility functions.

But but, what about async..

Async typically starts to clutter up actions really quickly. Saga's are really worth a look if you want to clean up your actions, but do introduce a certain learning curve if you're team isn't across generators and whatnot yet. In which case, thunks are still useful, and remember you can bundle up multiple async functions into a single promise that a thunk can play off (which again, that grouped promise can be separated out into a utility function class - like generateSaveSequencePromise() which might wrap up 4 or 5 async fetches, and return a single promise).

Side note - you should minimize dispatching multiple times from a single stream like in your first example. If possible, try to create a parent action that groups the entire action into one. So using your very first example, it might be:

// action
dispatch({type: "FETCHING_USER_IMAGE", id: userId });

And then your various reducers should clear their message queues, or whatnot if they "hear" that type come through.


There are advantages/disadvantages if you go with either way. But my options is to have reducers simpler than actions/actions creators.

Handling business logic in Reducers (Keeping actions simpler)

Perform all your synchronous business logics in your reducers keeping actions/action creators simpler.

Advantages

  1. Based on your business logic, you can decide how your next app state. should be.
  2. It is easy to handle business logics within reducers.

Disadvantages

  1. You can only perform synchronous tasks (but there are middlewares which supports async tasks).
  2. No access to dispatch.
  3. You can't access the whole app state if you had split your reducers.

Handling business logics in Action Creators (Keeping reducers simpler)

You can simply perform some business logic and trigger an action whenever you want.

Advantages

  1. You have access to dispatch.
  2. You can perform async tasks (see redux-thunk).
  3. Action creators have access to whole state (redux-thunk) even if you have combined the reducers.

Disadvantages

  1. No easy way to have business logics which works based on all the actions. For example, if you want to attach ID for all the actions, then you have to have a function attached with all actions

Here it is discussed in detail.


If you have your actionCreators complex, calling lots of little dispatches of little actions, you end up slowly moving towards your state effectively having 'setters' (each action becomes just a setter for a particular field). Where an actionCreator has wide-spread effects, you end up having to dispatch 'setter' actions that are relevant to various different sub-reducers - you're pushing the domain knowledge for these different sub-reducers up into the action creator. When you change a sub-reducer, you'll have to find all the action creators that dispatch related actions and adjust them appropriately.

With fat actions that just indicate what has happened (e.g. a request has been made, a successful response has come back, a timer has fired, a button was clicked), you can manage changes to business logic without needing to change the actionCreators. For example, you decide you want to start displaying loading masks during operations - you don't now need to update all your action creators to fire loadingStart and loadingEnd actions, you just update your reducers to set the loading state appropriately.

Personally, I think the best model for a complex application is to make actionCreators trivial (or drop them entirely, and just dispatch action payloads directly from your connected components) and instead use redux-saga (https://github.com/yelouafi/redux-saga) to handle your async and impure stuff - it's a much more powerful model. For example, it makes it easy to do things like debouncing and throttling actions, react to changes in store state as well as to actions, etc.