UNPKG

rescope

Version:

Flexible state management system based on flux architecture, stores data components & inheritable scopes

296 lines (224 loc) 8.46 kB
# ReScope Stores ## Stability (the Store:apply function) When state updates occurs, a store state stop being coherent with its results data.<br> In ReScope stores, the role of the "apply" function is to make the store data predictable basing on its state. This is done in 2 way : - In a synchronized way, by returning a new data hashmap from the "apply" fn ```jsx /*...*/ apply(data, new_state, changes_in_state){ let newData = {...data, ...state}; /* do synchrone interpolation & remaps in newData here */ return newData; } /*...*/ ``` - In an async way : - using this.push(newData) or by returning a referenced hashmap - and mostly by using wait() & release() paired functions, which will keep the store unstable and lock its update propagation until all wait() calls becomes released. ```jsx /*...*/ apply(data={}, new_state, changes_in_state){ let {async1={}, async2={}, async3={}} = data; /* do async fill / update the data */ this.wait('anAsyncRabbit') doSomeAsync( (err,res)=>{ async1.datum=err||res; this.release('anAsyncRabbit') } ) return {async1, async2, async3}; } /*...*/ apply(data={}, new_state, changes_in_state){ this.wait('anAsyncRabbit') doSomeAsync( (err,res)=>{ this.push({async1:res, status:err||'ok'};// will replace data this.release('anAsyncRabbit') } ) return data; } /*...*/ ``` ## A Store Workflow - A Store have it's state updated ( action has pushed state update or a source store had its data updated ) - If this state have the required & followed value - The apply function push new data in an async or sync way - The store is stabilized and (if there is new data) propagated - listening stores have theirs state updated and we go to step 1 until the whole scope is stable ## Stores initial state / restore A store initialized with data will be stable synchronously when instantiated. <br> If it only have a state but no data, the apply function will be called by the constructor synchronously. <br> (implying that the Store object may not be fully initialized) <br> ## Actions & mutations As the store stay independents, they deal with theirs own perimeters. <br> The app state could be mutated using different methods depending the needs. ### Using actions Actions could be dispatched from scopes or directly on the stores. \* They are only called if theirs stores are active. ```jsx class AppState extend Store{ static use = ["!AppConfig"];// require AppConfig to be applied & propagated static actions = { activateSalt(arg){// binded on the store instance // return some state updates return {some:'mutations'}; // or return; // to not change the state // wait, release, setState & push remain callable // this.nextState contain the incoming state } } } ``` ### Using setState All stores inherit the setState method. <br> Once a store state is updated, the resulting data changes are automatically propagated to the followers. ### Using stores functions The stores could be enhanced with functions & setters, that will ultimately update theirs state-data pairing. ```jsx class AppState extend Store{ static use = ["!AppConfig"];// require AppConfig to be applied & propagated static data = {}; switchTodoList(todoUrl){ this.setState({todoUrl}) // or this.wait(); doSomeAsync(()=>{ this.data.stateChange = "stand" this.release() }) } } ``` ### push Using push will update & propag the data of a store. * This should be used with cautious as it could break the state-data coherence. (that said not all the stores needs to be predictable) ## Stores state & data serialization / restoration Serialization & restoration is managed by the Scopes objects.<br> Stores only have to maintain the state-data coherence, but can have initial state and data from different sources :<br> - Using initial state & data : ```jsx class MyStore extends Store { static state = {};// initial state static data = {};// initial data (soft cloned) }; let MyStoreInstance = new MyStore( BaseScope, { state : {}, // take static defined state/data precedence data : {} } ) ``` ## How to add dependencies in a store ```jsx export default class currentUser extends Store { static use = ["appState", "session"];// here the source store that should be in the store scope apply( data, { appState, session }, changes ) { /*...*/ return data; } }; ``` ## How to "remap" dependencies value & sub-values in the state/data ```jsx export default class myInterpolatedDataStore extends Store { static use = { "someSource.someValue" : "mySwitchValue", "someSource2.someStuff.value" : "mySwitchValue2", "someSource3.someValue" : "mySwitchValue3", "someSource4.someValue" : "mySwitchValue4" }; apply( data, { mySwitchValue, mySwitchValue2, mySwitchValue3, mySwitchValue4 }, changes ) { /*...*/ return data; } }; ``` ## How to keep a store unstable until some stores / value is initialized ```jsx export default class myInterpolatedDataStore extends Store { static use = { "!someSource.someValue" : "mySwitchValue",// require someSource.someValue != false "!someRequieredSource" : true, "someSource2" : "someSource2" }; apply( data, { mySwitchValue, mySwitchValue2, mySwitchValue3, mySwitchValue4 }, changes ) { /*...*/ return data; } }; ``` ## How to only call apply & update the store if specific changes occurs in the sources store ```jsx export default class myInterpolatedDataStore extends Store { static use = { "!someSource.someValue" : "mySwitchValue",// require someSource.someValue != false "!someRequieredSource" : true, "someSource2" : "someSource2" }; static follow = {// only call "apply" if one of these state keys has change "someSource2":(newData)=>returnTrueIfApplicable(newdata), "mySwitchValue":true, // just change } apply( data, { mySwitchValue, someSource2 }, changes ) { /*...*/ return data; } }; ``` ## How to choose if data changes should be applied ```jsx export default class myInterpolatedDataStore extends Store { static use = { "!someSource.someValue" : "mySwitchValue",// require someSource.someValue != false "!someRequieredSource" : true, "someSource2" : "someSource2" }; hasDataChange( newDatas ) { return super.hasDataChange(state);// default : compare old and new data & data.* } apply( data, { mySwitchValue, someSource2 }, changes ) { /*...*/ return data; } }; ``` ## How to choose if data changes should be propagated ```jsx export default class myInterpolatedDataStore extends Store { static use = { "!someSource.someValue" : "mySwitchValue",// require someSource.someValue != false "!someRequieredSource" : true, "someSource2" : "someSource2" }; shouldPropag(data){ return true; } apply( data, { mySwitchValue, someSource2 }, changes ) { /*...*/ return data; } }; ``` ## How to catch synchrone errors in the apply fn ```jsx export default class testErrorCatch extends Rescope.Store { static state = { ok: true }; static actions = { makeError: v => ({ failNow: true }) }; apply( data, state ) { if ( state.failNow ) throw new Error("oups") return state; } handleError(error) { this.push({ failNow: false, catched: true }) } } ```