riot-js

Open full view…

Reactive Data Store

mattmc
Sun, 22 Oct 2017 19:57:09 GMT

The only data store package I can find for Riot is RiotControl, which is true to the spirit of Riot in that it is super-minimalist and imposes no controls on how you go about using it. However, I'm looking for something a little more automatic, requiring less plumbing, shall we say? There's Redux, which comes across as hopelessly complex. There's MobX, which requires annotations to be smooth (I don't use Babel or any transpiler) and still requires some mental overhead. I stumbled across VueX, which is their batteries-included state management solution and is attractively simple, but it seems to be bound to concepts in Vue such as computed properties. Then I found Moon, which is a simplistic Vue clone, and it has its own little state management package, with reactive updates done in a few dozen lines of code. It's MIT-licensed, so I took the code and adapted it to work with Riot. I'd like some feedback on this--good/bad idea, pitfalls, anything I'm missing? ``` // The Source of Truth! The Rabble. // // Mix a Rabble into your tags, like so: // // var store = new R abble({ // state: { // count: 10, // }, // actions: { // increment: function(state, payload) { // state.count += payload; // } // }, // }); // // riot.mixin(store); // function Rabble(options) { var store = this; // State store.state = {}; store.defineProperty("_state", options.state, {}); // Actions store.defineProperty("actions", options.actions, {}); // Tag instances to update store.tags = []; // Dependency map store.map = {}; // Component to capture during rendering cycle store.target = undefined; // Initialize reactive state. // Note that all properties of state must be defined ahead of time for them to become reactive. initState(); // Init is called by an instance of a tag when it loads a mixin. this.init = function(opts) { var tag = this; tag.name = tag.root.tagName; // Add the store to the tag so it's accessible from within expressions tag.store = store; tag.on('update', function() { store.target = this.name; }); tag.on('updated', function() { store.target = undefined; }); tag.on('unmount', function(){ // un-register with the store store.tags.splice(store.tags.indexOf(tag), 1); }); // Add to set of tags to update store.tags.push(tag); } } Rabble.prototype.dispatch = function(name, payload) { this.actions[name](this.state, payload); } Rabble.prototype.defineProperty = function(prop, value, def) { if(value === undefined) { this[prop] = def; } else { this[prop] = value; } } // Makes the store reactive Rabble.prototype.initState = function() { var store = this; var state = store.state; var _state = store._state; var instances = store.instances; var map = store.map; for (var i = Object.keys(_state).length - 1; i >= 0; i--) { var key = Object.keys(_state)[i]; Object.defineProperty(state, key, { get: function() { var target = store.target; if (target !== undefined) { if(map[target] === undefined) { map[target] = {}; } map[target][key] = true; } return _state[key]; }, set: function(value) { _state[key] = value; for (var i = 0; i < instances.length; i++) { var currentInstance = instances[i]; if(map[currentInstance.name][key] === true) { currentInstance.update(); } } } }); } // end loop } ``` That's it! Now you have a reactive data store. Write your tags like so: ``` <counter> <div onclick={handleIncrement}>{store.count}</div> handleIncrement(){ this.store.dispatch("increment", 1); } </counter> ``` When you click the counter, the data store will automatically make the tags re-render. At least, that's the idea.

skwny
Sun, 22 Oct 2017 20:43:52 GMT

I didn't go thru your code but storing the state and updating the tags is the way to go. I converted to Redux some time ago and am very pleased with it. At the time when someone recommended it, I thought the same way you do. You're right, there's a bit of overhead in getting started but once you're past that, it's very, very useful. It does for me essentially the same thing you are doing: it stores state and the app is subscribed to changes in the state, with each component getting what's relevant to it. Switching to es7 will also pay in dividends (oh that sweet, sweet `async/await`). My 2 cents is to think about how your thing will scale (if that's important to you). If you need it to scale and it will scale nicely with your thing, go for it. If it has the potential to become unorganized, unwieldy, and all generally more difficult to use, it's worth it to consider taking on the overhead of a framework.

mattmc
Sun, 22 Oct 2017 21:22:58 GMT

I am 99% sure that I will never use Redux; I picked Riot over React specifically because it was simpler, and I've been searching for the Riot equivalent to Flux/Redux ever since. Since you're familiar with Redux, I'd appreciate it if you looked through the code and see how it differs and maybe give a blurb on the tradeoffs. I don't necessarily see a problem in the way my code will scale, which is why I'm looking for external perspectives.

skwny
Sun, 22 Oct 2017 21:54:21 GMT

I understand. I wish I knew of a Riot-simple state management tool. I won't really understand what your code is doing without going through it and figuring it out (I'm not a pro and that will take me a bit of time which I don't have at the moment). However, here are a couple of thoughts for using a state management tool that I have come to learn about in the past year: 1. Is the state that your tool provides accessible across the app or only within your mounted tag? I think this block is checked since you're creating a data store solution. So in that case you're good to go. If it's only accessed within the tag instances, then that is not necessarily a bad thing, just a limitation. 2. Is the state only tied to the tags such that you would not modify state outside the tags? This question is a bit broader in scope. The reason I ask is because I build services to fetch data, and that data builds my state tree that I reference throughout my app. It used to be the case that a tag would call the service and that tag would conceptually represent a branch in the state tree (e.g. `<toDo>` tag would have it's own store under `state.toDo`. I later realized this was a problem because my state tree was really a representation of my database, and the tags were just ways of interacting with that, with multiple tags needing access to the same state. So I now do most of my heavy operations on my local app state/data store in common data fetching services outside of my tags . Since I use Redux, I also dispatch operations on relevant parts of the state object from within tags, without the tag being the sole owner of that part of the state. This has turned out to be a more scalable approach for me. There are still instances where tags get their own branch of state. Like I have a 'container' tag that holds all of my app tags. I use that tag to do things like turn on a loading indicator, display a toast, things like that. So it would be something like `state.container.display_toast = {// stuff}`. Hope that helps at least a little.

baxterw3b
Tue, 09 Jan 2018 22:24:09 GMT

Guys feel free to try my approach, maybe you'll like it, i'm using only the riot observable and mixin to create a centralized store. https://gitlab.com/baxterw3b/riot-es6-template

yjchan
Mon, 17 Sep 2018 17:01:48 GMT

Have you ever tried riotx? It's similar to vuex, which I think it's simple and effective