My team has been using Redux for a couple of months now. Along the way I've occasionally found myself thinking about a feature and wondering "does this belong in an action-creator or a reducer?". The documentation seems a bit vague on this fact. (Or perhaps I've just missed where it's covered, in which case I apologize.) But as I've written more code and more tests I've come to have stronger opinions about where things should be and I thought it would be worth sharing and discussing with others.
So here are my thoughts.
Use selectors everywhereThis first one is not strictly related to Redux but I'll share it anyway since it's indirectly mentioned below. My team uses rackt/reselect. We typically define a file that exports selectors for a given node of our state tree (eg. MyPageSelectors). Our "smart" containers then use those selectors to parameterize our "dumb" components.
Over time we've realized that there is added benefit to using these same selectors in other places (not just in the context of reselect). For example, we use them in automated tests. We also use them in thunks returned by action-creators (more below).
So my first recommendation is- use shared selectors everywhere- even when synchronously accessing data (eg. prefer myValueSelector(state)
over state.myValue
). This reduces the likelihood of mistyped variables that lead to subtle undefined values, it simplifies changes to the structure of your store, etc.
I think this one is very important although it may not be immediately obvious. Business logic belongs in action-creators. Reducers should be stupid and simple. In many individual cases it does not matter- but consistency is good and so it's best to consistently do this. There are a couple of reasons why:
Imagine your state has metadata related to a list of items. Each time an item is modified, added to, or removed from the list- the metadata needs to be updated. The "business logic" for keeping the list and its metadata in sync could live in a few places:
updateMetadata
action. This approach is terrible for (hopefully) obvious reasons.Given the above choices, option 3 is solidly better. Both options 1 and 3 support clean code sharing but only option 3 supports the case where list and/or metadata updates might be asynchronous. (For example maybe it relies on a web worker.)
Write "ducks" tests that focus on Actions and SelectorsThe most efficient way to tests actions, reducers, and selectors is to follow the "ducks" approach when writing tests. This means you should write one set of tests that cover a given set of actions, reducers, and selectors rather than 3 sets of tests that focus on each individually. This more accurately simulates what happens in your real application and it provides the most bang for the buck.
Breaking it down further I've found that it's useful to write tests that focus on action-creators and then verify the outcome using selectors. (Don't directly test reducers.) What matters is that a given action results in the state you expect. Verifying this outcome using your (shared) selectors is a way of covering all three in a single pass.
anshumanf, elrumordelaluz, IvoPereira, guilhermesimoes, bdcwq and 412 moresamthui, Amurmurmur, milkersarac, daxaxelrod, geekvijay and 6 moresabahang, prateekjahead, Chenzilla, regmish, imamatory and 32 moreerlendve, dcarral, itoldya, ATakaSKY, ruby97 and 42 more
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4