Watch paths on redux's store.
The objective of this utility is to watch specific paths on redux's store. This
is achieved by either providing a selector
function whose results will be
compared between current and previous state, or a string path whose value will
be checked on the current and previous state.
All differences are checked with strict equality. If there is a need to do
an equality check with anything other than strict equality, watch
accepts
an optional config checkEqual
, which accepts current and previous values.
$ npm install --save redux-store-watch
Include the package.
;
or
const watchStore = ;
store
.
Import your redux store. Assume imported as Using Redux component as example:
Here is how we would watch some string path's on the store.
{thiswatcher = ;this;this;// We don't have to watch specific values, we can just watch for any changes// on store's `user` path. Note that this watcher would also be called if either// of the previous watchers were called, as if `user.email` or `user.name` has// changed on an immutable store, `user` has necessarily changed.this;// We can also use a custom equality checker.this;}{// We unsubscribe from the store. If you do not do this, in the same way as// if you had no unsubscribed from redux, the callbacks would continue to// be called even if the component were no longer mounted.thiswatcher;}
Here is how we would watch the results of a selector.
{thiswatcher = ;// Let's say this is my selector, could be defined in another file. The// selector will always be provided the current state as an argument.const userEmailSelector = stateuseremail;this;// If you have a selector that requires specific arguments be passed in,// you can simply wrap the selector in a closure.// Say that this was my selector.const userValueSelector = stateuserprop;// Because my selector is expecting something different than just the `state`// argument passed in by the watcher, I can just wrap it in a function.this;// As a last example, each selector's result is actually stored since the last// change, so you could really put whatever you want as a function whose// result you'd like to compare to last time a redux action was dispatched.this;}{thiswatcher;}
Logging
Reactive programming of this nature has a lot of benefits, but has the downside of often being hard to debug. If I add a watcher on a value change with no logging, side effects are occuring that would be very hard to track down.
One of the reasons redux is so easy to debug is because of the redux logger letting you know which actions were dispatched. Without it, actions could be reduced all over the place without an easy way to track down which actions even dispatched.
In order to mitigate this, you have the option to pass a name to the watcher, like the name of an action. If shouldDispatch or shouldLog are enabled, details including the name, selector, path (if applicable), previous & current values will be logged or dispatched as an easy way to track down which watchers have been triggered.
Naming Convention
A good convention I've found for naming your watcher is:
{ caller description } << { watched target description }
For a concrete example, imagine a structure where I had an auth
reducer somewhere
in my application, responsible for holding the active user token. In my Login View
,
let's say that I'd like to watch the token being set.
watcher.watch('shared.auth.token', (token) => {
// Do something about this token.
}, {
name: 'LOGIN_VIEW << AUTH.TOKEN'
});
So to enable logging/dispatching for an individual watcher:
const watcher = watchStore(store, {
shouldDispatch: true,
shouldLog: true
});
To be more fine grained, the same logging/dispatch config is available on a per-listener basis.
watcher.watch('shared.user.email', () => {
// Thing changed!
}, {
name: 'LOGIN_VIEW << USER.EMAIL',
shouldDispatch: true,
shouldLog: true
})
It is recommended that you use requireName: true
, and provide names, just
think how much easier redux is because of its logging!
Global Config
Global Config allows us to configure logging, dispatching on a global basis, shared by all watchers in your application.
You are also able to set the store here, if you have a single store and would rather just set it once during your bootstrapping process.
import watchStore from 'redux-store-watch';
watchStore.configureGlobal({
store,
shouldDispatch: true,
shouldLog: true,
requireName: true
});
Now when creating a new watcher I can simply do:
import watchStore from 'redux-store-watch';
const watcher = watchStore();
Without providing a store, the watcher will fall back on global.