We use React for most of our current web-frontends. For older React Apps we used the MobX (databinding-library for MVVM) and then moved over to Redux. But even with Redux we where not quite happy because we used Redux for nearly every button enable/disable state. We are also committed to TypeScript and its strong typings even for our actions and action-creators.
With this one ends up with a huge global state-tree maintained by a lot of reducers. Even small apps quickly had 20+ reducers. Each reducer serves multiple actions which needed their action definitions and their action-creators as you see in the TypeScript tutorial.
The main problem was not the amount of code but the maintainability and the readability of all this code. When one changed the user-interface one had to change the state-tree and all the actions and action-creators involved. If the UI-workflow changed more fundamentally, also the whole action and state-tree had to be re-thought, decoupled and refactored or re-built. This even in smaller apps (but with non-trivial UI) became a big issue sooner then later.
Another issue: for UI interaction we had to think in the event-sourcing pattern instead of a classic view-model (MVVM). Event-Sourcing is a good pattern but not really natural for UI-stuff like “when is which button enabled” or “what parts of the component are shown/hidden”. For this stuff its way over the top. Again, think of if EVERYTHING goes though action-creators, actions, reducers and finally into the state which then gets mapped to the React component properties.
We realised (thanks, chief) that we where way to slow implementing user-interfaces comparing to other stacks we used in the past.
Four of us took on this topic and sat together, evaluated different things (even MobX again) and we then came up with the following:
- Use Redux for global state – stuff that is used in different parts of the application.
Eg. central master-data fetched from the server, login-information, etc.
- Use React component state for local state only.
Eg. if a component must maintain a enabled-disabled button-state this can be done easily in the local React component-state (the setState()-thing and must not go though all the Redux code.
- Use the new React Context for related component state.
Eg. if a set of components are used to build a edit-form introduce a context on the top level component of the form and consume this within the sub-components like the soft-keyboard, advanced input-controls like editors, etc.
Its up to the developer to decide when to use Redux and when React Contexts. Its not a hard line. Think of Redux-State as the central singleton-store where the React Context is scoped state that gets dropped afterwards. Further more the context can be hierarchically cascaded. So if your state’s nature is more hierarchically and temporary you probably want to use the React Context instead of Redux. Same applies if you like to build a view-model with data and UI-logic in one place you’ll go with React Context. On the other hand, if things need to be persisted or you like to use history (eg. the Redux dev-tools) you will choose Redux.
We practiced this at a few placed and quickly felt way more productive and used way less lines of code for the same UI. The result was especially mind-blowing when we changed a set of components that build up a complex value editor (used for touch-only devices used in a PWA) to use the React Contexts instead of Redux. We could remove a ton of code and the code get way more readable. Implementing new features to this value-editor became a relatively easy task. Using Redux the new editor-features require us to rebuild large parts of the state-handling code (and its tests) because actions, reducers and state is decoupled.
With the above three recommendations we quickly felt to be more productive in building and maintaining the UI while not loosing testability of our UI logic.
A few words about Redux
Don’t get me wrong. This is not a post against the use of Redux. We still like Redux and its Dev-Tools a lot. A really nice and transparent thing that makes debugging relatively easy. We don’t drop Redux entirely. We just don’t use it for everything anymore and therefore reducing its usage to a reasonable usage.
So, use Redux wisely, young Padawan 🙂