If you're like me and feel that there has to be an easier way of state-management, then you'd like what ActiveJS can do for you.
I feel like I'm selling snake-oil, but I spent the last 10 months trying to make state-management as intuitive and easy as possible because I couldn't stand the state-management in the state it is right now.
For efficient state-management, we need a few things
- data structures that are type-safe
- data structures that can emit events on mutation
- data structures that can guarantee immutability
- data structures that can be persisted through sessions
The title promised all this in one line of code, so here it is.
const dataUnit = new DictUnit({
id: 'data', immutable: true, persistent: true, cacheSize: Infinity,
initialValue: {a: 1}
})
// every option is optional, including the initialValue
// DictUnit has empty object {} as it's default value
(Okay 4 lines, but I formatted it so you don't have to scroll :)
JavaScript doesn't have anything like that, that's why ActiveJS came into existence, and with it came reactive data structures called Units, one of them is DictUnit, that stores and ensures a dictionary object value at all times.
You might have already got a feeling from the configuration options we passed to the DictUnit and guessed what it's all about, but to elaborate DictUnit is:
- Observable
- Reactive
- Type-Safe
- Immutable
- Persistent, and
- Cache-Enabled
Let's see what that means in the language we all understand, the code:
Observable
DictUnit extends RxJS Observable class, so you can subscribe to it and apply all the RxJS operators on it just as you would on an Observable.
// subscribe for the value
dataUnit.subscribe(value => console.log(value))
// logs {a: 1} immediately and will log future values
dataUnit instanceof Observable; // true
Reactive
When you update the value of a DictUnit it emits it to all the observers so that they get access to the latest value.
// non-functional dispatch
dataUnit.dispatch({b: 2, c: 3})
// observers get {b: 2, c: 3}
// now dataUnit's value is {b: 2, c: 3}
// functional-dispatch
dataUnit.dispatch(value => {return {...value, d: 4}})
// observers get {b: 2, c: 3, d: 4}
// we don't have to dispatch new values manually,
// DictUnit provides a better way to update properties
// update a single property
dataUnit.set('d', 5)
// observers get {b: 2, c: 3, d: 5}
// delete properties
dataUnit.delete('b', 'd') // 'b' and 'd' got yeeted
// observers get {c: 3}
// update multiple properties
dataUnit.assign({a: 1, b: 2})
// observers get {a: 1, b: 2, c: 3}
Type-Safe
A DictUnit ensures that at all times the value is always a dictionary object, it'll ignore any invalid value dispatch.
dataUnit.dispatch(['let', 'me', 'in']); // won't work
dataUnit.dispatch('let me in'); // won't work
dataUnit.dispatch(420); // won't work
dataUnit.dispatch(null); // won't work
dataUnit.dispatch(new Date()); // won't work
dataUnit.dispatch(() => new Date()); // won't work
There are 5 other Units just like DictUnit in ActiveJS, ListUnit to store array, NumUnit to store number, StringUnit to store string, BoolUnit to store boolean, and GenericUnit to store anything.
Immutable
The immutable flag makes sure that the DictUnit doesn't let the value get mutated in any way. Let's try to mutate it anyway.
const newValue = {c: 3};
dataUnit.dispatch(newValue) // works, value is {c: 3} now
// try mutating the newValue
newValue.c = 'hehe' // works, but
dataUnit.value() // still {c: 3}
// let's try a different approach
const currentValue = dataUnit.value() // {c: 3}
currentValue.c = 'gotcha' // works, but
dataUnit.value() // still {c: 3}
Persistent
The persistent flag makes the DictUnit persistent, such that whenever its value is updated, it saves that value to LocalStorage, so if we reinitialize a DictUnit with the same id and persistent: true flag, the DictUnit will restore its value from LocalStorage.
dataUnit.dispatch({c: 4}) // saved in LocalStorage
// after refreshing the browser-tab or reinitializing the DictUnit
dataUnit.value() // {c: 4}
// it restored the value from LocalStorage
Cache-Enabled
What if I told you that we can go back to all the previous values we just updated in previous examples, and then come back to the current value, yup Time-Travel is possible. All you need to provide is how many steps you want to be able to go back using the cacheSize option, by default it keeps 2 values and supports up to Infinity.
// let's reinitialize the Unit to demonstrate cache-navigation
const dataUnit = new DictUnit({
cacheSize: Infinity, initialValue: {a: 1}
})
// now let's dispatch a bunch of values to fill the cache
dataUnit.dispatch({b: 2})
dataUnit.dispatch({c: 3})
dataUnit.dispatch({d: 4})
dataUnit.dispatch({e: 5})
// now the value is {e: 5}, and
// the cache looks like this [{a: 1}, {b: 2}, {c: 3}, {d: 4}, {e: 5}]
// go back 1 step
dataUnit.goBack()
// now value is {d: 4}
// go back 2 steps
dataUnit.jump(-2) // negative means back, positive means forward
// now value is {b: 2}
// jump to the last value in cache
dataUnit.jumpToEnd()
// now value is {e: 5}
// jump to the first value in cache
dataUnit.jumpToStart()
// now value is {a: 1}
// go forward 1 step
dataUnit.goForward()
// now value is {b: 2}
That's it, folks, all done.
There are still a few things that we haven't covered that DictUnit can do, and we also haven't covered things like managing asynchronous API calls. But maybe that's a topic for the next article.
In the meantime, stay safe, try to have fun, and head over to ActiveJS website or documentation to learn more about how it can help you manage state with minimum effort.
Here's the StackBlitz playground link if you want to try it out yourself.
Here's a link to the visual playground, which you can try out without writing any code.
Also, I forgot to tell you that this is my first ever article on any platform, please let me know if I did an okay job, or if there's something that I can improve.
Cheers
๐ ActiveJS Website
๐ ActiveJS Documentation
๐คพโโ๏ธ ActiveJS Playground
๐ป ActiveJS GitHub Repo (drop a โญ maybe :)
Next Read: Asynchronous State Management with ActiveJS
Oldest comments (45)
That's one heck of a first article! Bravo!
this means a lot, thank you :)
What about github.com/grammarly/focal ? In addiction Focal can lift any react component to receive props as observables.
Never heard about it, just checked it out, "focal" looks great, it's a very similar approach I must say. From what I could gather, it only works with React, ActiveJS has no such affiliation as of yet, that can be a good or bad thing depending on who you ask. We don't have any plans to create special bindings for any framework, but it's something that we can think about. Thanks for sharing this.
One line... plus 1.64MB to import the library, and likely add dependencies on top of that. Ludicrous
only 8kB gzipped, and it's tree-shakeable too, that's what would go to production, not all the 1.64MBs of documentation, typings, and unpacked code.
Thank you for the good effort.
Yes it is 8KB gzip, will also need to add 11kb for rxjs (but this is ok for those who have use case for rxjs).
The next step is to make the library have as little dependencies as possible. One that does not have breaking changes or at least are easily manageable.
my pleasure :)
not quite correct, those 8kBs include all the RxJS dependencies that ActiveJS has, and 8kBs are for projects that are going to use ActiveJS without a module bundler, the target use case of ActiveJS is going to be modern Web-Apps built with modern frameworks, all of them have a module bundler like webpack, to eliminate any unused/dead-code from the bundle, see en.wikipedia.org/wiki/Tree_shaking.
Dependency on RxJS is a feature not a bug ;)
Since the ActiveJS Units extend RxJS' Observable class, it gives you the possibility to use all the myriads of RxJS operators.
Why so angry man? You just read the title and get straight here to comment without taking the time to analyze the work of the author?
Adding on to my last comment, I'd just like to say that, while I may not may not use this myself (I only really do front-end development at work, and that's almost exclusively limited to modifying existing apps), I think this is just a really cool project.
Regardless of current bundle size, etc., having more people thinking about state management and trying to improve it just strikes me as a really good thing.
It's okay if you can't use it, the encouraging words are good enough :)
Regarding the bundle size, I guess there's some confusion, the 8kB is the complete library bundled together (without tree-shaking) along with RxJS dependencies. If you're using a module bundler or already using RxJS in your project, the final build size contribution will be much lower.
This is an impressive library. Thanks for the share :).
Your first article is just... awesome! Well done man!
About your library, I'm very impressed with so much love you put into it: beautiful website, incredible documentation with examples to easily verify how it works.
I'm very happy with MobX at the moment in my projects but I certainly will try your library in future projects as I really love how simple it is.
thank you :)
I didn't have much else to do, but still it wasn't easy, thanks for noticing :)
Making it simple was the objective, "Pragmatic not Idealistic" is the motto.
Glad that you find it that way :)
Careful Ankit, you might just make people actually start enjoying state management if you carry on like this.. (:
All jokes aside, this is a very nicely written article though, thanks for the ActiveJS intro! :D
Hey Andre, wouldn't that be something ;)
thank you :)
I'm just glad that I didn't mess up
Interesting, glad I found this.
very impressive design -- I will investigate it further.
Hey Ankit! Really nice article, thanks for sharing this! Wish I knew this a few months ago.
Just a little correction: in the Type-Safe heading you typed
disptachinstead ofdispatch. It's not a big deal, but just to let you know in case you didn't notice that.Have a great day and I'm already following you! :)
hey Rodrigo,
my pleasure, and I wish could have finished it sooner :)
damn, didn't know I was dyslexic, thanks for letting me know, jk ;)
fixed it
you too, thank you :)