One dirty redux devtools logging trick
- redux
- javascript
While redux is far from being the coolest kid in the playground at the moment (late 2020), I am still using it on some of my projects. There is still one feature that most of its competitors (like zustand, jotai, recoil, react-query or SWR) lack so far: mature and feature-rich devtools.
But even on redux-devtools, there was something that made me think, “meh…”, and that thing is logging constants. Even though I made my redux constants less GENERATED_CODE_LOOKALIKE and more human readable (thanks mostly to ducks for inspiration), I’d like to be able to parametrize them.
// These constants are quite pleasant to read, but they are still, well… constant. const FETCH_PROJECTS_OF_SINGLE_YEAR_INITIALIZED = "[projects] Fetching projects of single year has started" const FETCH_PROJECTS_OF_SINGLE_YEAR_SUCCESS = "[projects] Fetching projects of single year was successful" const FETCH_PROJECTS_OF_SINGLE_YEAR_FAILURE = "[projects] Fetching projects of single year has failed"
On the devtools pane, it looks OK; not great, not terrible:
However, if I dispatch a lot of these actions in a row, I can’t help but think that something is missing here:
As it turned out, it would be quite easy! Just turn your constants into functions that receive an argument and return a string utilizing it:
const FETCH_PROJECTS_OF_SINGLE_YEAR_INITIALIZED = year => `[projects] Fetching projects of year ${year} has started` const FETCH_PROJECTS_OF_SINGLE_YEAR_SUCCESS = year => `[projects] Fetching projects of year ${year} was successful` const FETCH_PROJECTS_OF_SINGLE_YEAR_FAILURE = year => `[projects] Fetching projects of year ${year} has failed`
Well, OK, then, but what about action reducers? No worries! In the case of actions, just make their type property a function call and pass your argument in:
const fetchProjectsOfSingleYearInit = year => ({ // Note 'type' is a function call type: FETCH_PROJECTS_OF_SINGLE_YEAR_INITIALIZED(year), year, }) const fetchProjectsOfSingleYearSuccess = (projectsOfSingleYear, year) => ({ // Note 'type' is a function call type: FETCH_PROJECTS_OF_SINGLE_YEAR_SUCCESS(year), projectsOfSingleYear, year, }) const fetchProjectsOfSingleYearFailure = (error, year) => ({ type: FETCH_PROJECTS_OF_SINGLE_YEAR_FAILURE(year), error, })
As for the reducers, there is the same pattern: make your case a function call and pass your argument. It will match as expected:
export default function reducer(state = {}, action = {}) { switch (action.type) { case FETCH_PROJECTS_OF_SINGLE_YEAR_INITIALIZED(action.year): return { //... }; case FETCH_PROJECTS_OF_SINGLE_YEAR_SUCCESS(action.year): return { //... }; case FETCH_PROJECTS_OF_SINGLE_YEAR_FAILURE(action.year): return { //... }; default: return state; } }
And voilà, your redux devtools actions console is now much more informative (and fun):
I really appreciate this when debugging a lot of actions of the same type dispatched with different parameters. More than that, it can reveal possible bugs caused by unexpected arguments passed into actions (e.g., react-router params from a mistyped url).
The idea of making constants dynamic may sound like heresy to some hardcore Redux developers. I would argue that:
- The pattern is deterministic, with only pure functions used.
- It is meant to be used rather temporarily, for the purpose of debugging.
So far, I haven’t encountered any issues using it.
👍 Enjoy!
If you find anything in this post that should be improved (either factually or in language), feel free to edit it on Github .