Radixx
This is a simple Javascript library that implements the Facebook Flux Architecture with a twist to how the entire application state is managed, changed and updated. It resembles Redux in a lot of ways. The key deferentiator in both is that Radixx utilizes an actions stack and Redux utilizes an immutable state tree. A single state tree can grow big really fast for a single store but an actions stack grows subtlely for a number of stores when dealing with complex single-page applications(SPAs). The actions stack allows Radixx to recalulate any state at anytime on demand. It is also how Radixx maintains it's immutablility.
File Size
The footprint for Radixx is really small. With all it's functionality, it's just 19KB when minified!
- 48.5 KB (Current Version - Unminified)
- 19.3 KB (Current Version - Minified)
How to Use - < 2 steps >
- First, download/install it from NPM or Yarn
$ npm install radixx --save
$ yarn add radixx
- Next, use it in your JavaScript application like so:
Radixx (Vanilla JS) - Example App <!-- relative path (NodeJS) <script type="text/javascript" src="node_modules/radixx/dist/radixx.min.js"></script> --> <!-- absolute path (Unpkg) via CDN --> UNDO
DOCUMENTATION - Radixx APIs (Methods)
These are methods defined on the global Radixx object
- Radixx.attachMiddleware( Function middlewareCallback ) : void
Used to intercept actions that are dispatched by action creators to stores and/or modify action data where necesssary before they reach the store.
- Radixx.isAppStateAutoRehydrated( void ) : boolean
Used is interogate the auto-rehydration process to find out if the application state data was restored from the persistent storage on the client-side or not.
- Radixx.makeActionCreators( Object actionTagMap ) : object {RadixxActionCreator}
Used to create an action creators (in Flux Architecture parlance). An object literal having the action method names (as key) and the action tag (as value) is passed into this api method.
- Radixx.makeStore( String storeTitle, Function storeCallback [, Array|Object initialStoreState] ) : object {RadixxStore}
Used to create a store to which action will be sent using action creators.
- Radixx.purgePersistentStorage( void ) : void
Used to delete all the persisted data for auto-rehydration.
- Radixx.onShutdown( Function shutdownListener ) : void
Registers a listener that is called whenever Radixx automatically destroys all its stores internally due to a
shutdown-href
trigger.
- Radixx.onDispatch( Function dispatchListener ) : void
Registers a listener that is called whenever an action is disptached to the Hub (also called the
Dispatcher
in Flux Architecture parlance).
- Radixx.eachStore( Function storeIterator ) : void
Used to call a function on each store created by Radixx and perform some operation on that store (operation is defined with the storeIterator).
- Radixx.configure( Object configurationObject ) : void
Used to configure the Radixx store(s).
----------------------------------------------------------------------------------------------------------------
Radixx Configuration Options
- runtime.spaMode {boolean} [ EXPERIMENTAL FEATURE ]
Whether or not the app in an SPA (Single Page Application). If the app is a SPA, then the shutDownHref option doesn't have any effect.
- runtime.shutDownHref {string} [ EXPERIMENTAL FEATURE ]
The Href/Link upon which Radixx should destroy all stores and automatically shutdown state tracking. Also triggers the "Radixx.onShutDown()" handler function set prior to call.
- universalCoverage {boolean}
Makes it possible to replicates state changes across multiple browser tabs/windows
- persistenceEnabled {boolean}
Makes it possible to persist application state across browser session
- autoRehydrate {boolean}
Makes it possible to restore persisted state data into all pre-existing stores
----------------------------------------------------------------------------------------------------------------
Radixx APIs (Properties)
- Radixx.Helpers
Used to provide ancillary logic to SPA framework/library compoenents (e.g. ReactJS)
- Radixx.Payload.type.boolean
Used to signify that action creator method argument/payload MUST always be of type: Boolean
- Radixx.Payload.type.string
Used to signify that action creator method argument/payload MUST always be of type: String
- Radixx.Payload.type.array
Used to signify that action creator method argument/payload MUST always be of type: Array
- Radixx.Payload.type.object
Used to signify that action creator method argument/payload MUST always be of type: Object
- Radixx.Payload.type.date
Used to signify that action creator method argument/payload MUST always be of type: Date
- Radixx.Payload.type.function
Used to signify that action creator method argument/payload MUST always be of type: Function
- Radixx.Payload.type.number
Used to signify that action creator method argument/payload MUST always be of type: Number
- Radixx.Payload.type.nullable
Used to signify that action creator method argument/payload MUST always be of type: Null or Undefined
- Radixx.Payload.type.any
Used to signify that the action creator method argument/payload CAN BE of any type EXCEPT: Null or Undefined
- Radixx.Payload.type.numeric.Int
Used to signify that action creator method argument/payload MUST always be a Integer number
- Radixx.Payload.type.numeric.Float
Used to signify that action creator method argument/payload MUST always be a Floating-Point number
----------------------------------------------------------------------------------------------------------------
Store APIs
These are methods defined on the store object from Radixx.makeStore( ... ) call
- store.hydrate( Array|Object initialStoreData )
Used to put/insert/overwrite state data in the store directly without using an action creator. This call affects only the store in context. However, whenever actions are dispatched by an action creator, they affect all stores in the web application.
- store.getTitle( void )
Used to retrieve the storeTitle used in creating the store.
- store.getState( void|String stateKey )
Used to retrieve the current state of the store.
- store.makeTrait( Function traitFactory )
Used to create data items which are based on the store.
- store.setChangeListener( Function changeListener )
Used to register a listener (callback) that is called whenever the state of the store changes
- store.unsetChangeListener( Function changeListener )
Used to unregister a listener (callback)
- store.undo( void|Number timeSpan )
Used to undo a state change to the store
- store.redo( void|Number timeSpan )
Used to redo a state change to the store
- store.swapCallback( Function newStoreCallback )
Used to hot swap / replace a store callback
- store.destroy( void )
Used to remove store state data from sessionStorage (manually)
- store.disconnect( void )
Used to disbale the store from recieving dispatch from action creators methods
Features
- Infinite/Finite Undo/Redo + Time Travel
- Use of Traits (as Mixins) for ReactJS and VueJS (even though most people think mixins are dead and composition should be the only thing in used, i still think mixins have a place)
- Can replace the store callback (similar to replaceReducer() in Redux)
- Can overwrite store state data on the fly using hydrate()
- An extended Loose Coupling between Radixx Dispatcher and Controllers/Components.
- A transparent way for separating mutation, dependence and asynchronousity in application state management.
- No need to emit change events from within a store registration callback.
- No unecessarily elaborate definition for Action Creators & Stores (Write less code).
- No need to use immutableJS library to help manage application state changes (Require state on demand).
- The Dispatcher object is hidden and all you have is a Hub object.
- Includes an onDispatch event that exposes the entire global application state (from all stores).
Benefits
- Use with Single Page App Frameworks/Libraries e.g. VueJS 1.x/2.x, AngularJS 1.x, Angular 2.x, Ractive, ReactJS, jQuery
- Use with Multi Page App Frameworks as well e.g Django, Jollof, Pheonix, Laravel, Flask, AdonisJS (application state persists across page(s) load/reload)
Best Practices (Dos and Don'ts)
- Application UI State {a.k.a Volatile Data} -- don't store this in Radixx stores (e.g. Text Input - being entered, Animation Tween Properties/Values, Scroll Position Values, Text Box Caret Position, Mouse Position Values, Unserializable State - like functions)
- Application Data State {a.k.a Non-Volatile Data} -- do store this in Radixx stores (e.g. Lists for Render fetched from API endpoints, any piece of Data displayed on the View)
NOTE: When using Radixx with ReactJS, it is best to ascribe/delegate Application UI State to {this.state} and {this.setState(...)} and Application Domain Data State to {this.props} respectively. One reason why Radixx recommends this approach is to avoid confusion as to when {this.setState} calls actually update both the DOM and {this.state} since {this.setState} is sometimes asynchronous in the way it operates.
Caveats/Gotchas
- Radixx would not maintain state across multiple tabs in a browser except the universalCoverage config option is set to true.
- Radixx throws an error if the runtimeMode config option
- Always call the Radixx.configure() method before creating action creators and stores. If you don't want to change the default configuratio for Radixx, simply call Radixx.configure({}).
- Radixx would not access the storage objects where necessary if it is placed in an iframe that has very tight sandbox attribute restrictions or does not have the same scheme and domain as the parent window (same-origin policy restrictions) except a reverse proxy tactic or the domain hack is used.
About Redux (with respect to Radixx)
Hers's how Redux is simply described
Redux is basically event-sourcing where there is a single projection to consume the application state (think CQRS - Command Query Responsibilty Segregation).
Someone had this to say about the state of Redux single store
I'm also not impressed about every action having to go “all the way” upwards (to the central store) instead of short-circuiting somewhere.
That being said, here is a round-up of what makes Redux a GREAT tool (for some use cases) and what doesn't make it so ideal (for all use cases)
Gains of Redux single store
- Infinte Undo/Redo + Live-Editing Time Travel (As application state is immutable).
- Predictable Atomic Operation on Application state object (As actions are run in a specific predictable order).
- Single source of truth (No Guesswork!!) for applicaton state.
Same applies to Radixx
Troubles with Redux single store
- A sizable amount of boilerplate code (especially with connect() and/or mapStateToProps() from react-redux project library) is required to get Redux up and running.
- Dynamically structured state is impossible. (mature, complex apps need this the most).
- Increased probability of state key(s) collisions between reducers (very likely in a big complex web app but highly unlikely in samll ones).
- Global variables are always a bad thing (This applies to the composition of the Redux application state itself) as you could
clobber
them unknowingly. - Performance suffers as your state tree gets larger (Immutability is a good thing...sometimes).
- Each time the connect() decorator is called, it pulls in the entire application state (when using react-redux project library).
Your best bet in all these is to choose the trade-offs wisely (depending on the peculiarities of the web app you're building). In order words, Choose what you can live with and what you can't live without when it comes to having a store in your application.
NOTE: Radixx is not an outright drop-in replacement for Redux or Vuex but a nice alternative when Redux or Vuex doesn't quite fit your use case.
Tests
This project uses Jasmine for tests and runs them on the command line with Karma .You can run to check the quality of code written by running the tests in 2 ways
- run the following command in the root folder after cloning the repo
$ npm install $ npm run test
- open the tests/specRunner.html file in any browser of your choice using the file:/// protocol
Examples
VueJS 1.x/2.x
var action_creators = Radixx; var store = Radixx; var trait = store; // A Vue Component defined with lifecycle hooks via mixins (Radixx store traits) const MyVueComponent = name:'stuffs' /* The name of the component could be the same as that of the store */ mixins:trait prop: claim: type:String required:true template:`<div> <p>{{componentState.profile.fullName}}</p> <input type="text" v-model="firstName"> <input type="number" v-model="phone"> <button v-on:click="add" v-bind:disabled="buttonDisabled" v-bind:title="gender">ADD</button> </div>` computed: { return store; } watch: { // code goes here... } { /* Only UI state (transient) should go in here No core application state should be here */ return buttonDisabled:false firstName:'' phone:'' ; } methods: { /* calling an action to write data into our store and trigger an update on the view/store change event, here we are writing to `fullName` with a value of `this.firstName` */ action_creators; } ); /* For communication between one or more components, the props and events of VueJS come in very handy */ // See: https://vuejs.org/v2/guide/components.html#Composing-Components var app = el:'#app' components: 'my-component':MyVueComponent ;
AngularJS 1.x
If you are using Radixx with AnugularJS / Angular 1.x, then you need to include the provider made available in this repo (ng-radixx.js) and imprt/inject it into the config
function like as below using '$ngRadixxProvider' name.
// Top-level Module { Using the Angular 1.x Provider Helper - $ngRadixx } angular; // Domain-level Module angular ; ; ;
ReactJS
/* ==================== */ /*! * When using Radixx with ReactJS, There are few things you should be aware of. * * * ReactJS and Radixx are not opinionated. This can make it tricky * to use the two together and you can get it all wrong very easily. * * Using the concept of Root, Presentation and Container components - adapted from Dan Abrahamov (creator of Redux) * it is possible to get it right when using ReactJS with as many as 12 components in a project. If you have at most * 2 components in your project then you need not use Radixx or if you do you don't really need to organize your * components into ROOT, PRESENTATION and CONTAINER components. Below is a list of guidelines to safely use Radixx * with ReactJS * * # Only use `setState` on the ROOT and PRESENTATION Components * # Only use `componentWillMount` on the ROOT Component (There MUST BE only 1 ROOT Component per App). * # Only use `shouldComponentUpdate`, `componentWillRecieveProps`, `componentDidMount`, `componentWillUnmount`, `getDefaultProps` and `mixins` on CONTAINER Components * # Only use `shouldComponentUpdate`, `componentWillRecieveProps`, `componentDidUpdate`, `getDefaultProps` `getInitialState` and `mixins` on PRESENTATION Components * # The STORE should only be attached to CONTAINER Components * # The ACTION_CREATOR should only be attached to PRESENTATION Components * # The `render` method can be used on all 3 Component Categories (ROOT, PRESENTATION and CONTAINER) * # Only use `makeTrait` method of STORE(s) to create Mixins for the CONTAINER and PRESENTATON Components * # Only insert UI state (which is transient and non-persistent) in the state object of PRESENTATION Components * # The `setState` method MUST NEVER be used in CONTAINER Components * * You can also do without CONTAINER components and have just ROOT and PRESENTATION componets for a simpler web application * */ /* ===================== */ /* The code belwo uses ReactJS v0.13.1 - */ // Asuuming to use socket.io (client-side) var socket = io /* Radixx Action Creator */ actions = Radixx /* Radixx Store */ store = Radixx; /* ReactJS - Presentation Component Mixin */ const PresentationTrait = store; /* ReactJS - Container Component Mixin */ const ContainerTrait = store; /* ============================= ============================= */ /* PRESENTATION COMPONENT */ // defined with most lifecycle hooks /** @jsx React.DOM */ var ShoeComponent = React; /* ============================= ============================= */ /* CONTAINER COMPONENT */ // defined with no lifecycle hooks /** @jsx React.DOM */ var ListingsContainer = React; /* ============================= ============================= */ /* ROOT COMPONENT */ // defined with fewer lifecycle hooks var TheApp = React; ReactDOM;
Browser Support
- IE 9.0+ (Trident)
- Edge 13+ (EdgeHTML)
- Opera 11.6+ (Presto, Blink)
- Chrome 4.0+ (Webkit, Blink)
- Firefox 4.0+ (Gecko)
- Safari 7.0+ (AppleWebkit)
Gotchas/Caveats
-
Trying to
swap
the store callback using the swapCallback() method before setting a store listener using setChangeListener() will always throw a type error. -
Whenever you instantly fill up a store by using the hydrate() method, the store callback is NEVER called. Only middlewares and store state change listeners are called. Also, the data in the store is overwritten with that passed as an argument to the hydrate() method.
-
It's COMPULSORY to call Radixx.configure() before your application code loads (after loading the Radixx library) else Radixx may not work properly or as expected. If you would like to setup Radixx with the default config simply call as below:
Radixx;
License
MIT
Live Projects { that use Radixx for state management in production }
- NTI Portal - National Teachers Institute Kaduna - Applicants Section
- Stitch NG - Fashion Technology Product
Contributing
Please feel free to open an issue, fix a bug or send in a pull request. I'll be very glad you did. You can also reach me on Twitter @isocroft. See the CONTRIBUTING.md file for more details.