riot-redux-connect v1.1.8
riot-redux-connect
Moving from React + Redux to RiotJS + Redux, I've been unable to find
a convenient way to use Redux store in Riot tags.
I've glanced through a number of approaches and packages,
but all of them lacked granular tag updates
and their API wasn't convenient enough for me.
This package aims to provide a base for effortless connection
of your Riot tags with your Redux store in maintainable
and performant way. Whenever your store updates, your tags will be updated
granularly i.e. only tags that are accessing changed parts
of the Redux store will be updated, not the whole application.
riot-redux-connect attempts to be consistent with
react-redux connect function.
Although not all react-redux features are currently implemented,
riot-redux-connect still covers most popular usecases.
Missing react-redux features could be implemented in the future
(if there will be demand on them).
Installation
riot-redux-connect requires riot 2.3.16 or later.
npm install --save riot-redux-connectThis assumes that you’re using npm package manager
with a module bundler like Webpack
or Parcel to consume
es6-modules.
riot-redux-connect version to use without module bundler
is not provided currently.
It may be added in the future though (if there will be demand on it).
riot-redux-connect also uses WeakMap es6-feature for memoization.
Babel does not transpile it, so if you're supporting non-es6-browsers
you should:
- either make sure that you provide
WeakMappolyfill (it is included inbabel-polyfill) - or provide custom memoization function
in
memoizeByFirstArgReferenceparameter (will be explained further).
Example
index.js:
import riot from 'riot';
import riotReduxConnect from 'riot-redux-connect';
import store from './store';
riotReduxConnect(riot, store);
document.body.innerHTML = `<some-tag></some-tag>`;
riot.mount('some-tag'));some-tag.tag:
import { someAction, otherAction } from './action-creators';
import { selector, otherSelector } from './selectors';
<some-tag>
<pre>{opts.infoReceivedFromRedux}</pre>
<pre>{opts.otherInfoFromRedux}</pre>
<button type="button" onclick={someAction}>Some action</button>
<button type="button" onclick={otherAction}>OtherAction</button
<script>
function mapStateToOpts(state) {
/*
* selectors are functions that are receiving state plain object
* and returning plain object with the data derived from state;
*
* selectors may ease the pain of refactoring state shape;
* they can also improve perofrmance by leveraging memoization;
* for further info see https://github.com/reactjs/reselect
*/
return {
infoReceivedFromRedux: selector(state),
otherInfoFromRedux: otherSelector(state),
}
}
const mapDispatchToMethods = {
someAction,
otherAction,
}
this.reduxConnect(mapStateToOpts, mapDispatchToMethods);
</script>
</some-tag>Usage
riot-redux-connect provides only one (default) export:
riotReduxConnect(riot, store, [options])
A factory function that binds Redux-store to Riot global context.
Binding is implemented in special tag method (documented further),
provided via riot global mixin,
that this function creates under the hood. Although the name of this method
could be configured, we will refer to it as reduxConnect for convenience.
So, you can use reduxConnect method on any tag you want to have
connected to the Redux-store.
Arguments
riot(Object):Riotglobal contextstore(Object):Redux-storeoptions(Object): If specified customizes the behavior ofriotReduxConnect; it accepts following options:mixinName(String): a method name to use in global mixin instead ofreduxConnectdefaultOnStateChange(Function): a function to be called on each granular tag update by default (eachreduxConnectcall may override it on per-tag basis).Main purpose of this function is to attach
Redux-extracted data (let's call itstateOpts) andRedux-dispatch-bound methods (let's call themdispathMethods) to the tag instance.Function is called with tag context as
thisandstateOptsanddispathMethodsas arguments.Default function implementation merges
stateOptswith tag'soptsand assigndispatchMethodsto be the direct methods of tag instancedefaultImplicitDispatchOptName(String): an opt name to use by default to putReduxdispatchmethod to, when no explicitmapDispatchToMethodsargument is passed toreduxConnectdefaultReduxSyncEventName(String): defines the name for the special event, which triggers re-calculation of redux-dervied opts and dispatch methods of the tag. Default to simpleredux-syncvalue.This is mostly needed when you have selectors that depend on tag opts, and once those opts are updated you need to somehow get an updated redux-derived props and dispatch methods. You can also override this special event's name on the per-tag basis (see option
reduxSyncEventNamefurther).defaultDisablePreventUpdate(Boolean): iftruethe default behavior of preventingriot's' auto-updates in action creators will be disabled for all tags; (by default this option set tofalse)I.e.
riotautomatically updates tag after any DOM-event handler was invoked in the contents of this tag. When you're using action creator as an event handler, you're getting double update (one auto-update byriotand one auto-update byriot-redux-connect). That's whyriot-redux-connecttries to detect when action creator is used as an event handler and to automatically sete.preventUpdate = trueto get rid of excessiveriotauto-updates in those cases. You can disable this behavior by settingdefaultDisablePreventUpdatetotrue, if you are not agree withriotReduxConnectfor some reason. You can also override update-preventing behaviour on per-action-creator basis (see optiondisablePreventUpdatefurther).memoizeByFirstArgReference(Function): custom memoize implementation to use instead of the default one that usesWeakMap;memoizeByFirstArgReferenceshould accept a function and return its counterpart memoized by using first argument reference as a key.
reduxConnect([mapStateToOpts], [mapDispatchToMethods], [options])
This function is provided as a tag method (with configurable name)
by the means of riot global mixin
implicitly defined by riotReduxConnect.
It connects the tag on which it was called with the Redux store,
by:
- injecting the relevant data and methods into tag instance .
- updating the tag instance on every change to the store data that this tag instance uses
You can call it on any tag you want to have connected to the
Redux-store, but note thatreduxConnectmethod should be used:
- only once per tag (otherwise error will be thrown)
- prior to tag's initialization (i.e. do not use it inside tag lifecycle events)
Arguments
[
mapStateToOpts(state, [tagInstance])] (Function): If this argument is specified, the tag will subscribe toReduxstore updates. This means that any time the store is updated,mapStateToOptswill be called. The result ofmapStateToOptsmust be a plain object, which by default will be merged to the tags’sopts. If you don't want to subscribe to store updates, passnullorundefinedin place ofmapStateToOpts.tagInstanceargument can be used to access tag'sopts(or other properties/methods) in order to parametrize selectors somehow. Note, however, that if those properties/method return values will change on tag update,Redux-extracted data will not change immediately with them. It will only change after next dispatch.You can circumvent this by triggering
"redux-sync"event on tag instance whenever you need it. Beware of inifinite cyclical updates though; make sure that update loop will break at some point (memoizing your functions could help with that).The
mapStateToPropsfunction's first argument is the entireReduxstore’s state and it returns an object to be passed as props. It is often called a selector. Use reselect to efficiently compose selectors and compute derived data.[
mapDispatchToMethods(dispatch, [tagInstance])] (Object or Function): If an object is passed, each function inside it is assumed to be aReduxaction creator. An object with the same function names, but with every action creator wrapped into adispatchcall so they may be invoked directly, by default will be assigned to be the tag instance methods.riotReduxConnectis also checking if those action creators are used as DOM event handlers and in case they are, riot auto-updates is automatically disabled on them (unless you've setdefaultDisablePreventUpdatetotrue, of course).If a function is passed, it will be given
dispatchas the first parameter andtagInstanceas a second parameter. It’s up to you to return an object that somehow usesdispatchto bind action creators in your own way. (Tip: you may use thebindActionCreators()helper fromRedux). Note that riot auto updates does not disabled in this case, so beware of double-updates on connected tag!tagInstanceargument can be used to access tag'sopts(or other properties/methods) in order to parametrize action creators somehow. Note, however, that if those properties/method return values will change on tag update,Redux-dispatch-bound data will not change immediately with them. It will only change after next dispatch.You can circumvent this by triggering
"redux-sync"event on tag instance whenever you need it. Beware of inifinite cyclical updates though; make sure that update loop will break at some point (memoizing your functions could help with that).If you do not supply your own
mapDispatchToMethodsfunction or object full of action creators, the defaultmapDispatchToMethodsimplementation just injectsdispatchinto your tag’sopts.options(Object) If specified, further customizes the behavior of thereduxConnect. In addition to the options passable toriotReduxConnect(see those above),reduxConnectaccepts these additional options:onStateChange(Function): a function to be called on each granular tag update.Main purpose of this function is to attach
Redux-extracted data (let's call itstateOpts) andRedux-dispatch-bound methods (let's call themdispathMethods) to the tag instance.Function is called with tag context as
thisandstateOptsanddispathMethodsas arguments.Defaults to the
defaultOnStateChangevalue ofriotReduxConnect'soptionsargument.implicitDispatchOptName(String): an opt name to use to putReduxdispatchmethod to, when no explicitmapDispatchToMethodsargument is passed toreduxConnect.Defaults to the
defaultImplicitDispatchOptNamevalue ofriotReduxConnect'soptionsargument.reduxSyncEventName(String): defines the name for the special event, which triggers re-calculation of redux-dervied opts and dispatch methods of the tag. This is mostly needed when you have selectors that depend on tag opts, and once those opts are updated you need to somehow get an updated redux-derived props and dispatch methods.Defaults to the
defaultReduxSyncEventNamevalue ofriotReduxConnect'soptionsargument.disablePreventUpdate(String): array of names of the properties inmapDispatchToMethodsobject (formapDispatchToMethodsfunction this options does nothing), for which the default behavior of disabling riot's auto-update (on DOM event handler calls) should NOT work.Defaults to the
defaultDisablePreventUpdatevalue ofriotReduxConnect'soptionsargument.
Multiple stores example
Using multiple store is discouraged by Redux methodology, so please use this way only if you're know what you're doing.
index.js:
import riot from 'riot';
import riotReduxConnect from 'riot-redux-connect';
import store1 from './store1';
import store2 from './store2';
riotReduxConnect(riot, store1, {
mixinName: 'connectStore1'
});
riotReduxConnect(riot, store1, {
mixinName: 'connectStore2'
});
document.body.innerHTML = `<some-tag></some-tag>`;
riot.mount('some-tag'));some-tag.tag:
import { store1Action, store2Action } from './action-creators';
import { store1Selector, store2Selector } from './selectors';
<some-tag>
<pre>{opts.infoFromStore1}</pre>
<pre>{opts.infoFromStore2}</pre>
<button type="button" onclick={store1Action}>Store1 action</button>
<button type="button" onclick={store2Action}>Store2 action</button
<script>
this.connectStore1(
state => ({ infoFromStore1: store1Selector(state) }),
{ store1Action }
);
this.connectStore2(
state => ({ infoFromStore2: store2Selector(state) }),
{ store2Action }
);
</script>
</some-tag>License
MIT