Update May 20, 2020: For new code, prefer plain React for state management, via the React context and React Hooks. Is Redux deprecated? No, it isn't. But you probably don't need it, and your code can be simpler and more maintable without out.
In the last post of this series, I’ll demonstrate writing UI code as a set of interactions and share how this facilitates integrating Redux and Flow.
When I look at UI code, I want two things to be as obvious as possible:
What interactions are possible here (e.g., A, B& C).
If interaction A has happened, what changes did it make to the application state?
The clarity of these points is the key to the long and happy development of any UI application.
When my interactions were split between the actions.js and reducer.js modules, in order to read or write code, I was having to constantly switch back and forth between the two. Things were even worse if multiple reducers were involved. I realized that I should reorganize the code around the interactions, because either implementing a new or working on an existing one, I’m always working in the context of interaction, not action creators or reducers.
Based on this, I reorganized my folders into UI units, like this one:
|- components/ # representation
|- interactions/ # modules w/ interactions
|- selectors/ # data selectors
|--...|- state.js # state definition
|- actions.js # list of action creators
|- reducer.js # list of action handlers
|- index.js # container w/`connect`
The main idea here is to represent interactions as a modules.
The simplest possible case is when a synchronous action is dispatched and only one reducer should respond. For example, the interaction module below defines a behavior of modal dialog:
Notice the createReducer helper from Redux’s recipes. It makes it possible to have an exact mapping of a dispatched action from an action creator to the action handler in the reducer. It’ll be required for accurate flow typings.
Let’s say you requested to PATCH an entity and the server responded with 200 OK. At this point to respond on the single dispatch, you must apply 2 changes to the app state:
reset UI unit store (turn off spinner, reset form state, etc.)
All the changes in the app caused by the interaction are gathered in one place that’s easy to find, easy to reason about, and easy to change, move or remove. If a modal must be converted to inline element or a Google map must be removed: in each case, you’re dealing with files and folders dedicated to a given interaction instead of chunks of code scattered around disparate action and reducer modules.
When you’re working on google map interactions, you’re focused only on code related to the google map interactions. There aren’t any distractions from unrelated code.
One additional benefit here is the ability to accurately type Redux parts with Flow. Thanks to Atom, I can view Flow errors in the real-time in my editor. And thanks to Nuclide for superior Flow integration.
My goal in combining Redux & Flow was to prevent the following cases:
dispatching an illegal action to reducer [ example ]
setting an illegal property on the state object [ example ]
reading an illegal property from the state [ example ]
Here is the example app I’ll refer to during the rest of this post:
Here’s an example of the Flow warnings in action, when I refactor state property name from postId to id:
4. Typing action creators in representational components
Sadly, Flow can’t infer types of action creators defined in interaction modules. It is also impossible to manually share an action creator type because function signatures are different once they’ve been bound to dispatch by Redux. So, we have to re-type action creators manually in our components.
There are some more limitations; see the README for details.
Thanks for reading this, more great stuff coming soon. Stay tuned!