- Small (~1.5KB)
- Immutable from the outside, mutable in actions
- Reactive (state emits updates without explicit calls to i.e.
setState) - Modular (you can nest models inside each other)
I was disappointed with all the current state management solutions.Then I found mobx-state-tree, which seemed like a godsend to me (ok not really, but I liked the concept), but it was pretty big in terms of file size (mobx alone is big: 16.5kB).So I thought it's surely possible to make a smaller version of it, that's how this started.And after 2 failed attempts I finally got something that works well
// ES6import{model}from'parket';// CJSconst{ model}=require('parket');Note: This library uses Proxies and Symbols. Proxies cannot be fully polyfilled so you have to target modern browers which support Proxies.
import{model}from'parket';// model returns a "constructor" functionconstPerson=model('Person',{// name is used internally for serializationinitial:()=>({firstname:null,lastname:null,nested:null,}),actions:(state)=>({setFirstName(first){state.firstname=first;// no set state, no returns to merge, it's reactive™},setLastName(last){state.lastname=last;},setNested(nested){state.nested=nested;},}),views:(state)=>({fullname:()=>`${state.firstname}${state.lastname}`,// views are computed properties}),});// merge an object with the initial stateconstinstance=Person({firstname:'Tom'});// you can subscribe to actions, patches (state updates) and snapshots (full state after actions)constunsubscribe=instance.onSnapshot(console.log);// you can unsubscribe by calling the function returned by the listener// unsubscribe();instance.setLastName('Clancy');// views turn into cached gettersconsole.log(instance.fullname);// 'Tom Clancy'// nested models also bubble up events to the parentinstance.setNested(Person());instance.nested.setFirstName('wow');// you can get a snapshot of the state at any time// { firstname: 'Tom', lastname: 'Clancy', nested: { firstname: 'wow', lastname: null, nested: null }}console.log(instance.getSnapshot());constAsync=model('Async',{initial:()=>({loading:false,result:null,}),actions:(self)=>({asyncdoSomethingAsync(){// actions can be async, parket doesn't careself.loading=true;self.result=awaitsomethingAsync();// be aware that you should handle errorsself.loading=false;},}),});import{Component}from'preact';import{observe,connect,Provider}from'parket/preact';// or 'parket/react'// observe keeps the component updated to models in the prop@observeclassObservedextendsComponent{render({ person}){// if you're using react, props don't get passed to render so you have to use `const {person} = this.props;`return(<div><h1>{person.fullname}</h1></div>);}}// connect inserts the store/instance into props@connectclassPersonextendsComponent{render({ store}){// if you're using react, props don't get passed to render so you have to use `const {store} = this.props;`return(<div><h1>{store.fullname}</h1></div>);}}// Provider adds an instance to the contextconstroot=()=>(<Providerstore={instance}><divid="app"><Person/><Observedperson={instance}/></div></Provider>);functionObserved({ person}){useObserved(person);return(<div><h1>{person.fullname}</h1></div>);}functionPerson(){conststore=useStore();return(<div><h1>{store.fullname}</h1></div>);}// Provider adds an instance to the contextconstroot=()=>(<Providerstore={instance}><divid="app"><Person/><Observedperson={instance}/></div></Provider>);MIT © hrmny.sh