Mobx-State-Tree is an opinionated library that imposes a strict architecture on state organization.
State is stored in an immutable, structurally shared tree.
Business logic removed from the UI (views are passive) (decouple domain logic and stare from UI) Provides a structure for communication
Embrace composition and reactivity
Relay on the computed properties not on the model Subscribe to all the path streams (patches) Replay and record snapshots, patches and actions Protection against uncoordinated modifications Protection against stale reads Runtime type checking and inferred TypeScript static type checks, design time type checking
A good model makes it easier to deal with reality
State is not just data but identities and relations Separates consumption of data and changing of state
Everything that can be derived from state should be derived. Automatically
Break into smaller components if not fast enough
With observable data we know what changed and where it is relevant resulting in code which is straight forward to read and write, smart optimized rendering and easy separation of concerns
State requires mutability - it's impossible to build a meaningful application without mutability Control mutability and side-effects in an organized way (state management)
Observable objects are as a collection of streams, one stream per property
Use hooks for local state (if it's not needed anywhere outside the component)
Mobx is like a spreadsheet
- Observables - spreadsheet's data cells that have values (state than can be changed over time)
- Actions - the act of changing the values of spreadsheet's data cells (interactions that change state)
- Computed Values - formulas and charts that can be derived from the data cells and other formulas (values that can be derived)
- Reactions - drawing the output of a data cell or a formula (side effects that should respond to state changes)
observer
- turn components into reactive components
inject
- connects components to provided stores
Independent solution (use with anything) Distinct concepts: Side effect and derived values No change detection but change propagation Synchronous scheduling (helps with debugging) Optimally sorted dependency tree for guaranteed glitch-free, minimal amount of computations
A node can exist only once in a Tree Any node in a Tree is a Tree itself All leaves in the Tree must be serializable Every change to a model correspond to a snapshot and a patch
Tree consists of 2 thing:
- state (contents)
- shape (type)
const Type = types.model(properties, actions);
const instance = Type.create(snapshot);
instance.someAction();
const snapshot = getSnapshot(instance);
const Book = types.model(
{
title: types.string,
price: types.number,
},
{
setPrice(newPrice) {
this.price = newPrice;
},
}
);
const Store = types.model({
books: types.array(Book),
});
const store = Store.create({
books: [
{
title: "Some Title",
price: 29.99,
},
],
});
store.books[0].setPrice(99.99);
//composing types
store.books.push(Book.create({ title: "Some Title" }));
store.books.push({ title: "Some Title" });
//references
const cartEntry = types.model({
amount: types.number,
book: types.reference(Book), //it's not a Book, it's just a reference
});
cartEntry.book = bookStore.books[0]; //normalization done behind the scenes and reference management
One root store - can preform actions on everything at once, DI is shared
Multiple root stores - easier to reason by Domain (isolation), can't easily preform actions on everything, DI isn't shared
Each Store access directly other Stores
getParent(self).someStore
- each store knows the whole structure Actions wrapper - calls directly other Stores, knows the whole structure Dependency Injection - injecting stores into one another, can be used for both Actions and Views
const firstStore = FirstStore.Create({});
const secondStore = SecondStore.Create({ someProperty: "Foo" }, { firstStore });
When data is the same but the behavior is different
const thirdStore = types.compose(FirstStore, SecondStore);
- separation of concerns
- reusability
instances can only be modified through actions
actions can only modify own subtree
store.books[0].price = 99.99; //Error cannot modify the object is protected
Method invocation produces action description
//MST is fully recursive concept, every node in the tree is state tree in it self and any operation in the tree can be invoked on a node
const bookCopy = clone(store.books[0]);
const recorded = recordActions(bookCopy);
recorder.replay(store.books[0]); //atomic commit of all the changes the user made
import { getEnv } from "mobx-state-tree";
React Context - single event per context
Redux - global event, but selecting relevant parts
MobX - event per property, selection is automatic (subscribed to each individual property)
React state - component Redux - single store MobX - user defined objects/classes
Both Redux and MobX have decouple state from UI layer
Most bugs happen because poor references management - staleness bugs (store id reference not the object reference, identity or value)
- Wrap properties with getter and setter - tracks every interaction with an object
- Store running function in a stack
- Getters register observers
- Setters notify observers
- MobX optimizes dependency graph (small as possible, least amount of computations)
Transparent reactive programming
- decoupling of producers and consumers of information
- straightforward to write
- optimized, minimal dependency tree
Both React and MST are contract based (only props, views and actions are exposed, internal state encapsulated, design and runtime type checking)
Component - Type (React composes Components, Mobx composes Types)
const House = () => (
<Door />
<Window>
)
const House = types.mode({
doors: Door,
windows: Window
})
Props - Snapshot
State - Volatile state
Instance - Instance (React composes a tree of component instances, Mobx composes a tree of rich type instances)
const instance = React.createElement(Component, props);
const instance = Type.create(snapshot);
Context - Environment
Reconciliation - Reconciliation (preserve internal state, performance)
Snapshots are immutable, structurally shared representation of entire state at a specific moment in time (GIT Commit)
JSON Patch - deltas describing updates that were applied to the tree (Git Patch, describes modifications from one commit to the next) (used for time travelling like Git Revert, Git Rebase)
Middleware - intercept action invocations (Git hook)
Built in concept based on generators so middleware can run on every continuation
//same semantics, slightly difference syntax
async function - process(function*)
//same semantics, slightly difference syntax
await -yield;
Manage Application State with Mobx-state-tree
Manage Complex State in React Apps with MobX
Immutable JavaScript Data Structures with Immer
How do I work with state in React?
What is MobX-State-Tree and how can it help me with React?
How do I get started with MobX-State-Tree and React?
How do I modify state values in React with MobX-State-Tree?
How do I create views with MobX-State-Tree?
The Curious Case of Mobx State Tree
Why Infinite Red uses MobX-State-Tree instead of Redux
Understanding MobX and MobX State Tree
State Management with MobX State Tree
https://github.com/mobxjs/mst-gql
Bindings for mobx-state-tree and GraphQL Run scaffolding in your CI/CD against production endpoint to verify readiness
yarn mst-gql --format ts http://localhost:4000/graphql
Stop defending all the choices you don't make. "Didn't try" is fine. You have to learn to be able to use, but, you don't have to use to be able to learn. (read a bit, grab an idea, software engineering is all about patterns)
If the property was typed as Type | CreationType then all the properties unique to the Type (e.g. actions, views, etc) wouldn't be available when trying to use the property.
To overcome this you have to either cast it to the instance type (since internally mst will convert the snapshot to an instance) or create the instance directly.