mobxjs / mobx-state-tree Goto Github PK
View Code? Open in Web Editor NEWFull-featured reactive state management without the boilerplate
Home Page: https://mobx-state-tree.js.org/
License: MIT License
Full-featured reactive state management without the boilerplate
Home Page: https://mobx-state-tree.js.org/
License: MIT License
see also: #47
Just curious if this is planned/possible?
If the argument of an action is a Model object, it should be serialized as just the relative path to that model element. E.g.: { $path: "../todos/7"}
Note that only model elements belong to the same tree (same root) should be acceptable. Note that the arguments might be complex, so this process should be done by some traverse that clones all the plain data and replaces the models with references
I know @mweststrate mentioned in this issue: https://github.com/mobxjs/mobx/issues that better typescript support will be coming soon but I thought I would just start this issue to track my my definite interest in better typing support ๐
It seems that mobx-state-tree
is a way behind the pure Mobx + mobx-react in the terms of performance.
100000 to-do list example shows next results:
see this slides for more info
How the Performance can be improved? Any ideas?
Probably better: getParentModel
. For consistency I think we should talk about "models" everywhere, so also createModel
, composeModel
etc.
question: does that also hold for applySnapshot etc?
import {getParent, types} from 'mobx-state-tree'
const Store = types.define((self) => types.model({
todos: types.array(Todo), // without types.define, would be `null` at this point
subStores: types.array(self) // for recursive types
})
type IStore = typeof Store.Type // already known at compile time
const Todo = types.model({
title: "",
getParent(): IStore {
return getParent(this) as IStore
}
})
Store.finalizeType() // make sure the lambda is actually called
Currently only support for a single reference is supported
I know it's neat, but it breaks typings... :/
See this example:
(running on VSCode insider with noImplicitThis at true)
import {Type, types as t} from 'mobx-state-tree'
interface IIdentity{
id: string
token: string
}
const Identity = t.model({
id: t.primitive,
token: t.primitive
})
const AuthService = t.model({
user: t.maybe(Identity),
setLoggedUser(user: IIdentity){
this.user = user
}
})
As following these instructions:
npm install
npm start
open http://localhost:3000
ERROR in ./src/index.js
Module not found: Error: Cannot resolve 'file' or 'directory' /Users/john/Desktop/mobx-state-tree/examples/boxes/../../lib in /Users/john/Desktop/mobx-state-tree/examples/boxes/src
@ ./src/index.js 17:21-47
ERROR in ./src/stores/domain-state.js
Module not found: Error: Cannot resolve 'file' or 'directory' /Users/john/Desktop/mobx-state-tree/examples/boxes/../../lib in /Users/john/Desktop/mobx-state-tree/examples/boxes/src/stores
@ ./src/stores/domain-state.js 13:21-47
ERROR in ./src/stores/socket.js
Module not found: Error: Cannot resolve 'file' or 'directory' /Users/john/Desktop/mobx-state-tree/examples/boxes/../../lib in /Users/john/Desktop/mobx-state-tree/examples/boxes/src/stores
@ ./src/stores/socket.js 10:21-47
ERROR in ./src/stores/time.js
Module not found: Error: Cannot resolve 'file' or 'directory' /Users/john/Desktop/mobx-state-tree/examples/boxes/../../lib in /Users/john/Desktop/mobx-state-tree/examples/boxes/src/stores
@ ./src/stores/time.js 15:21-47
webpack: Failed to compile.
Example:
const Todo = createFactory({
author: {
name: "Michel"
}
})
(Ok, bad example).
author
would be a fresh object for each new Todo
.
I know that they are not full supported yet, but great polyfills exists, so are there problems with using something like WeakMap<object, Node>() to store the "nodes" and easily retrieve them?
I'm building an editor that supports putting "widgets" on a drawing surface. The widget types are not known to the editor (it will be reused in various contexts with different sets of domain-specific widgets).
So each widget is represented by a wrapper model that stores certain things common to all widget instances (coordinates, title). And then the widget-specific stuff is implemented by a separate object.
So the widget instance is serialised like:
{
left: 3,
top: 2,
width: 8,
height: 6,
title: "My widget",
type: "funWidget",
settings: {
// could be anything, depends on type
}
}
For deserialisation, a widget factory is injected into the editor and supports creation of a specific implementation given a type name, and then the settings
can be serialised into the implementation.
Is this kind of OO polymorphism something already supported by -state-tree, or is it something you are considering?
Hello,
Two-way data binding has a bad reputation in the React community, because it is easy to write code that will be hard to debug, and โunidirectional data flowโ is recommended instead of that pattern. But with a library like mobx-state-tree
we get use simple read and write expressions on a model field, myModel.field = newValue
, and still get all the benefits in term of development experience (reproducibility, debugging, etc.) of a Flux/Redux architecture.
So with that, I'm wondering if a set of helper functions to help write the read and write binding between a from input and a mobx model field would be useful, and what would be a desirable API.
So for instance here we bind a Toggle component from Material UI with the corresponding field on our app model:
import Toggle from "material-ui/Toggle"
import { bindToggle } from "mobx-two-way-data-binding" // Theoric API
import { observer } from "mobx-react"
import { myModel } from "../models"
export default observer(() =>
<Toggle label="Enable hyper mode" {...bindToggle(myModel, "isHyperModeEnabled")}/>,
)
In the above example the bindToggle
helper function would be defined as follow:
function bindToggle<T, K extends keyof T>(model: T, field: K) {
return {
defaultToggled: model[field],
onToggle: (event, value) => {
model[field] = value
},
}
}
I'd like to have some feedback about this idea of helper functions for binding a mobx-state-tree field with a form input, and discuss APIs. For instance there are different way of binding an input
element: we could use onChange
which would update the model every time a key is pressed, or wait for some form submission event. Also here I'm using object destruction to feed the input component props, but maybe a higher order component would be better?
I would like to inject unstructured (not modelled by a factory) JSON into my state tree (e.g., the JSON response from a web request).
Is that somehow possible? Or what would be the recommended way to store such kind of data (web requests being just one example)?
@mweststrate I have some concern about types and constructor functions (factories) in mobx-state-tree :/
The problem is that administration object are'nt much extensible right now.
There is no way right now to do something like "hey, take that factory administration object and enhance it like this.".
For example I was wondering about on how to implement the "unionOf" introduced by #21 with the addition of a field in the snapshot "$$type" or similar which is the field type, and found no answer, since right now there is no way of taking the child administration object and enhance it.
So maybe I was wondering; since administration objects are striclty related to patch emitting/applying and knowing who owns that node, maybe we should move some of the operation to the type?
The following snippet won't work right now, because arrayOf returns IObservableArray (createArrayFactory returns instead a factory) and same for mapOf.
I think that was made to make arrayOf and mapOf work with createFactory.
const Row = createFactory({
article_id: 0
})
const Collection = arrayOf(Row)
const coll = Collection()
I think that we could use something like this to avoid this problem. What do you think @mweststrate ?
export type NodeFactory<T> = (state?: T) => T
export type GraphType<T> = NodeFactory<T> | T
function structOf<T>(def: {[P in keyof T]: GraphType<T[P]>}): NodeFactory<T>{}
function arrayOf<T>(def: GraphType<T>): NodeFactory<T[]>{}
So, I looked into this project after zalmoxisus/mobx-remotedev#1 (comment)
Before I can comment there, I'm wondering how should the developer represent array of models with mobx-state-tree
?
Currently I have something like store.featuredProducts
which is an observable array. How does the resolution (resolve(this, '/users', this._author)
) works? Why do I need this
and identifier? Does it work only for resolving single objects? Can it batch resolve? Anyway, can you give an example how would that happen, so I visualize this best?
Another negative for me is the '/users' identifier. Using identifiers feels a step back for me. I would prefer if there is a instance of a ModelManager then can retrieve it by class/object like:
import {Author} from `authorfile`;
manager.get(Author, id);
Is there any particular reason that noImplicitAny is false in that project?
it brings compatibility issues with noImplicitAny: true projects
because SSR and such should be trivial with mobx-state-tree :)
based on the docs my understanding is that fields of a model cannot be directly mutated, but can observe :-) the following behavior:
const Test = mobxStateTree.createFactory({
field: 'hello',
change: mobxStateTree.action(function(f) { this.field = f; })
});
const test = Test();
mobx.autorun(() => console.log(`field: ${test.field}`));
test.change('world');
test.field = 'universe'; // should this work?
// prints:
// field: hello
// field: world
// field: universe <== ups?
Hi,
Im wondering a little bit about how this lib is supposed to be used exactly.
Given the following:
const Box = createFactory({
color: ""
})
const BoxStore = createFactory({
boxes: arrayOf(Box)
})
const boxStore = BoxStore()
const newBox = Box({ color: "red" });
boxStore.push(newBox);
console.log(newBox.color) // red
applySnapshot(boxStore, {
boxes: [
{ color: "blue" }
]
})
console.log(newBox.color) // should this be blue now?
Is this the intention?
Can I make an @observable of newBox
then use it in a react component and expect the component to be re-rendered when a property of newBox changes due to a snapshot being applied?
Cheers,
Mike
Make it possible to pass an environment when a state tree is created, to pass in dependecies like logging, data fetching etc:
const Store = types.model({
todos: [],
loadTodos() {
getEnv(this).fetch("/todos").then(data => {
this.todos = data
})
})
const store = Store.create(snapshot, { fetch: window.fetch })
detach
should preserve envclone
accepts second argument (keepEnv = true | false | newEnvironment
)Maybe I will feel embarrassed but I couldn't figure out how to use model composition with just a single child instance, not using mapOf()
or arrayOf
... :-(
What would be the right syntax to add a single masterBox
to the BoxStore
example in the main README
? I tried something like:
const BoxStore = createFactory({
masterBox: Box(),
boxes: mapOf(Box),
...
or
const BoxStore = createFactory({
masterBox: instanceOf(Box),
boxes: mapOf(Box),
...
I haven't found a single example for this case...
Support similar mechanism for js instanceof
for the models instances.
Another option would be to keep a reference to the factory on the model via well-known symbol.
My usecase: I have an react component that gets am array of models and needs to pick the component to use with a specific model
I want to do something like:
const componentsMap = new Map([factoryUser, ReactComponentForUserModel]);
...
render() {
return <div>
{this.models.map((m) => return <componentsMap.get(m.factorySymbol)/>)}
</div>
}
When i try to reuse my code on the java server under NashornScriptEngine i get this error:
javax.script.ScriptException: Error: Reaction doesn't converge to a stable state after 100 iterations.
import {createFactory} from 'mobx-state-tree'
import {useStrict} from 'mobx'
useStrict(true)
let TaskError = createFactory({
message: null,
})
class Validator {
taskDoneErrors (json) {
let taskError = TaskError({message: 'OK'})
return [{message: taskError.message}]
}
}
global.validator = new Validator()
When i use only mobx threre are no errors.
suggestions:
..But only if there are good use cased :-D
This is a question rather than an issue, and I apologize for my ignorance on the subject. I've been using Mobx since it was announced, so I've used it to mange the state in the majority of my projects. I recently started a React Native project utilizing Apollo and ex-navigation which both use Redux for state management. I think this project is the perfect mix of what I'm looking for (observable data which I can incorporate into Redux's store/middleware).
I haven't used Redux that often, but looking at the todo example it seems that you can create a Redux store from a factory. However, how would I go about integrating that factory/store into a current Redux store? Or, how could I take the current Redux reducers and integrate it into a factory?
Thanks, and the project looks amazing!
createFactory
seems to be quite impossible to make strongly typed in Typescript, without mapped types (even when not taking into consideration the partial subtypes of snapshots)
Two sample playgrounds demonstrating some of the problems:
factories are not functions, but expose (possibly null) create method (note the create!()
)
Something like: microsoft/TypeScript#13257 could definitely help!
This is the best I could do so far:
type Factory<T> = T & { create?(): T }
function createFactory<T>(base: T): Factory<T> {
return null as any
}
function primitive<T>(value: T): Factory<T> {
return null as any
}
const A = createFactory({
x: primitive(3 as number),
y: primitive("boe" as string)
})
// factory is invokable
const a = A.create!()
// property can be used as proper type
const z: number = a.x
// property can be assigned to crrectly
a.x = 7
// wrong type cannot be assigned
a.x = "stuff" // Red is ok
// sub factories work
const B = createFactory({
sub: A
})
const b = B.create!()
// sub fields have proper type
b.sub.x = 4
const d: string = b.sub.y
// sub fields can be reassigned
b.sub = A.create!()
Note that ones the type system is in place node type might need to change, in that case the instance should be replaced anyway. (This also holds for the existing object node keys)
Probably there should be some utility method:
updateOrReplaceNodeWithSnapshot(currentValue, snapshot): newValue
where newValue
is currentValue
updated with snapshot
if the snapshot is assignable and currentValue
non-primitive, and otherwise a new node and instance are created. This will probably replace a lot of the current maybeNode
calls
When dealing with large scale apps, is fails at top level. Instead, is should accept a second parameter (an empty array by default), and put the entire error stack of the check to provide detailed informations of where error happened. (like typescript does).
I have just prepared for one React Meetup where I will tell about different React Architecture. So I want to create a Tree
like an example of powerful mutable date manipulations with MobX.
For example, I want to create a tree with such Node
interface:
interface Node {
value: string;
children: Node[];
};
So in mobx-state-tree it have look something like this:
let Node = createFactory({
value: '',
children: arrayOf(Node)
})
But it is error for JS compiler.
In my opinion, it will be better to do some reference function to this structure:
const Node = createFactory({
ref: 'node'
value: '',
children: arrayOf(ref('node'))
})
Or some recursion function, like smart bind this function:
const Node = createFactory({
value: '',
children: recursionCollection(function () {
return arrayOf(this)
})
})
Or this will have a collection
method like:
recursionCollection(function () {
return arrayOf(this.collection)
})
// or function which receives collection as the argument
recursionCollection(function (collection) {
return arrayOf(collection)
})
Guys, what do you think. How will it be better to resolve this issue? I want to fix it :)
Would be nice to introduce an "either" type.
Its predicate will return the correct type to use based on snapshot state.
const Photo = createFactory({
src: ""
})
const Video = createFactory({
playback_src: ""
})
const User = createFactory({
name: "",
media: either(state => state.src ? Photo : Video)
})
createModelFactory({
done: false,
toggle() {
this.done = !this.done
}
})
clone
insteaddetach
Requires mobxjs/mobx#652
Calling subscribe could return a stream of snapshots for interoperability with Symbol.observable / RxJs etc
Right now, mobx-state-tree has a very basic type system. Basically, it knows if a node is an array, an object, a reference or a primitive.
Imagine this real world scenario:
I have a stacked navigation in my app, so I will store all my app screens state in an array named "stack" and I will push/pull instances from it. But this array will contain a lot of screens; I would say that in typescript would be an array of an union type of all the available application screens.
Sample TypeScript typing.
interface LoginForm{
type: 'login'
username: string
password: string
}
interface SignupForm{
type: 'signup'
username: string
email: string
email_repeat: string
password: string
password_repeat: string
}
type ApplicationForms = LoginForm | SignupForm
interface ApplicationState{
stack: ApplicationForms[]
}
This will need union types in mobx-state-tree. To implement them, we need a standard way to handle types in mobx-state-tree.
My proposal is to modify the factory definition in something like this:
export interface IModelFactory<S, T> {
(snapshot?: S, env?: Object): T & IModel
isModelFactory: true
factoryName: string
is: (snapshotOrNode: any) => boolean
dispatchType: (snapshotOrNode: any) => IModelFactory<any, any>
identity: boolean
config: Object
}
It basically introduces 3 new props:
NB: Types will always receive the snapshot as input to determine the type.
PS: Thanks to @gcanti because this environment reminded it's awesome work on tcomb!
Just to write down what to do! :)
Preparation
Type System
Actions
Refactorings
Testing
Performance Optimizations
Docs
DevTools
Hey, guys!
Thanks a lot for this great lib ๐
Can you clarify one question for me?
I have code:
const Item = createFactory({
id: types.number,
name: types.string,
});
const SomeScheme = createFactory({
items: types.array(Item),
});
const someScheme = SomeScheme(blabla);
const response = await this.fetch('/api/');
someScheme.items.replace(response.data);
Sheme from API:
[
{
"id": 2,
"name": "Name"
}
]
Everything is ok.
But then at the API backend guys adding new non-breaking (not for us) field.
For example:
[
{
"id": 2,
"name": "Name",
"description": "Text"
}
]
And we getting error and stack trace.
How can I avoid/handle this case?
Any solution for ignoring additional
fields for the scheme?
Trying to get started with mobx-state-tree, but it looks like PR #43 introduced some changes that breaks the code examples in examples folder and README?
mapOf
, arrayOf
etc is no longer working. I guess I should use something like
import { createFactory, types, action } from 'mobx-state-tree';
const { map, array, referenceTo } = types;
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.