GithubHelp home page GithubHelp logo

mobxjs / mobx-state-tree Goto Github PK

View Code? Open in Web Editor NEW
6.9K 87.0 640.0 13.4 MB

Full-featured reactive state management without the boilerplate

Home Page: https://mobx-state-tree.js.org/

License: MIT License

TypeScript 98.20% JavaScript 1.58% HTML 0.05% CSS 0.04% Dockerfile 0.02% Shell 0.11%
mobx-state-tree mst mobx state-tree redux observable snapshot state-management

mobx-state-tree's People

Contributors

adamkovalsky avatar ajaxsolutions avatar atsu85 avatar bnaya avatar brianhung avatar caesarsol avatar cmdcolin avatar coolsoftwaretyler avatar dependabot[bot] avatar dsabanin avatar dyingalbatross avatar jamonholmgren avatar jayarjo avatar jhen0409 avatar k-g-a avatar kga-kontur avatar mattiamanzati avatar mattruby avatar mondaychen avatar mweststrate avatar nickbreaton avatar o4epegb avatar pioh avatar pustovalov avatar raineratspirit avatar robinfehr avatar ssured avatar the-noob avatar vonovak avatar xaviergonz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mobx-state-tree's Issues

Complex action arguments should be (de)serialized

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

Performance improvement

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:
image
see this slides for more info

How the Performance can be improved? Any ideas?

Introduce forward types to support circular types

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

Drop toJSON() in models...maybe?

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
    }
})

Error trying to 'npm start' the boxes example

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.

consider using WeakMap

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?

Polymorphic collections

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?

Two-way data binding in forms

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?

possible to inject unstructured JSON into the state-tree?

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)?

Introduce primitive types

See #20 / #21

Primitive types are currently general, but there should be factories per primitive type (which are inferred correctly):

  • number
  • boolean
  • string
  • Date (how to serialize, epoch number?)

Nullable values are not acceptable, in such cases maybe should be used

RFC: Administration object are'nt much extensible.

@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?

Idea: Use TypeScript 2.1 to avoid wrong typings to make createFactory typings work.

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[]>{}

Representing array of models in the state tree

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);

noImplicitAny is false

Is there any particular reason that noImplicitAny is false in that project?
it brings compatibility issues with noImplicitAny: true projects

fields are mutable?

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?

Rewrite all invariants as `if ... fail()`

  • Gives better type inference
  • Can easier be disabled in future as optimization step
  • Faster; doesn't need to computed error message upfront
  • Hitting the assertion is properly reflected in coverage

Confusion in expected result

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

Idea: introduce dependency injection

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 })
  • should not be possible to make a tree part of another tree that was initialized with an environment (to avoid original env getting lost if object is replaced etc)
  • detach should preserve env
  • clone accepts second argument (keepEnv = true | false | newEnvironment)
  • Environment can be read from any descendent

model composition

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...

[Feature Request] Add a deterministic way to distinguish between models types

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>
}

Reaction doesn't converge to a stable state after 100 iterations.

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.

Introduce lifecycle hooks for models

suggestions:

  • beforeDispose
  • afterCreate
  • beforeApplySnapshot
  • afterApplySnapshot
  • afterCreateSnapshot
  • beforeChange (like intercept)

..But only if there are good use cased :-D

Integrating w/ current Redux Store

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!

Proper typescript typings

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:

fails to reassign subfields

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!()

Instances / nodes in arrays and maps should be recycled when applying snapshots

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

Fine grained "is()"

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).

Recursion createFactory collection

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 :)

Idea: Either types

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)
})

Don't require `action` keyword for actions

  1. it's cumbersome
  2. if you want utilities that don't have action semantics (for example to have utilities that receive complex args), those can be extracted to static helper methods
  3. makes syntax a lot nicer, as one could write:
createModelFactory({
   done: false,
   toggle() {
     this.done = !this.done
   }
})

Clarify and enforce tree semantics

  • can always assign from snapshots
  • can assign new instances / stand alone trees
  • cannot assign partial trees (could create graph), throws, use clone instead
  • can detach from tree using detach

Idea: Basic type system

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:

  • is: Given a snapshot or a Node, it returns if the snapshot is assignable to that Model Factory type.
  • dispatchType: Given a snapshot that is assignable to this type, resolve the exact type. In union types this will resolve to the correct subtype.
  • identity: If true, no dispatch is needed and you can assume that dispatchType(snapshot) will return the type itself. Primary types, arrayOf(), mapOf() are identiy. Union types are not.

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!

Roadmap

Just to write down what to do! :)

Preparation

  • Refactor and move factories

Type System

  • Basic type system #21
  • Introduce Type #20
  • Primitive type #24
  • arrayOf(Type)
  • mapOf(Type)
  • withDefault #16
  • Refinement types #43
  • Specific primary types (string, number, boolean) #43
  • Constant type (matches true when the value is exactly the same is: arg => value === arg) #36
  • maybe(Type) ( unionOf(null, Type) #36
  • unionOf(Type, Type, ...Type) #17
  • Frozen type (matches any serializable type. It's factory will be an identity function checking for serialization and then froze the argument)
  • Array of References
  • Map of References
  • References #55

Actions

  • Serialize complex arguments as refs #55
  • Should actions allow to change only their node/child nodes? (redux does so, is really needed or it just enforces encapsulation?)

Refactorings

  • Move types to their own namespace #43

Testing

  • (almost) 100% code coverage

Performance Optimizations

  • When applying a snapshot, find a more efficient way.
  • 3-pass applySnapshot (get type tree, check snapshot, apply it)

Docs

  • Rewrite examples
  • Documentation
  • Getting Started
  • FAQ

DevTools

  • Connect to redux-devtools

Additional fields

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?

Update docs to reflect breaking changes in #43

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;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.