MobX Depot Logo

MobX Depot

Docs

Get started
Models
Queries
Mutations
Root store
Caching
React hooks

Models

Each object type in your GraphQL schema is converted into two things: a BaseModel class, and a Model subclass. The BaseModel class contains the fields from your schema, as well as some helper methods (we'll get into that later.)

For example, given the following schema:


type User {
id: ID!
firstName: String!
lastName: String!
email: String!
}

This will generate the following BaseModel:


// Do not edit any BaseModel! They are generated automatically and will be overwritten.
export class UserBaseModel {
@Selectable() declare id: ID;
@Selectable() declare firstName: string;
@Selectable() declare lastName: string;
@Selectable() declare email: string;
// ... constructor, helper methods, etc...
}

And the following Model:


// Feel free to add any fields or methods! Try not to touch the constructor, however.
export class UserModel extends UserBaseModel {
constructor(init: Partial<UserModel> = {}) {
super(init);
makeModelObservable(this);
}
}

You can instantiate a UserModel like so:


const user = new UserModel({
firstName: "John",
lastName: "Doe",
email: "johndoe@example.com"
});

Adding logic to a model

You can add any fields or methods to your Model class. For example, you can add a fullName computed property:


export class UserModel extends UserBaseModel {
constructor(init: Partial<UserModel> = {}) {
super(init);
makeModelObservable(this);
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}

This is super valuable because any data returned from mutations and queries will get deeply converted into their respective Model instances. This means you can conveniently access any fields or methods you've added to your Model class.

Mutating data within a model

You can mutate data within a model by using the built-in set method:


const user = new UserModel({});
user.set('firstName', 'John');
user.set('lastName', 'Doe');

You can also use the assign method for merging an object into the instance:


const user = new UserModel({});
user.assign({ firstName: 'John', lastName: 'Doe' });

What is @Selectable?

The @Selectable decorator adds logic that throws an error if you try to access a field that was not selected from a query. This helps you avoid optional chaining and other null checks all over your code.

For example, given the following query:


query {
user {
id
name
}
}

If you try to access user.email, you'll get an error:


// Imagine `user` is a `UserModel` instance that was returned from a query
user.email; // Error: Field "email" is not selected

This is ideal, as it keeps your code clean and prevents your logic from silently producing invalid results.

Getting selected data

If you'd like to get an object containing the data that has been selected, you can use the selectedData getter:


const userQuery = new UserQuery(user => user.name.email);
userQuery.dispatch().then(user => {
console.log(user.selectedData); // { id: '1', name: 'John', email: 'johndoe@example.com' }
});

What is makeModelObservable?

You may have noticed that generated models use makeModelObservable from mobx-depot instead of makeAutoObservable. MobX has limitations regarding the use of makeAutoObservable with subclasses. makeModelObservable works around this limitation by determining the required annotations by traversing over this and its prototypes, then providing them to makeObservable.

Do not use this in your own code. This works for us because:

  • We're not extending an external class
  • BaseModels are designed to be inherited from, and are not meant to be instantiated directly

See relevant MobX discussion for more details.

wontfix

I'd like to emphasize @urugator's comment:

[...] this is not a suggested way to go, therefore I don't want to promote it in any way. Whatever you expose, some people will use it, twist it, misuse it, and in the end it will always come back to you, no matter the warnings. Automatic behavior provides convenience for common/simple cases. If it can't support inheritance reliably, the responsible thing to do is either not introducing automatic behavior in the first place (no longer an option) or to forbid cases that could lead to issues.

I am quite convinced that the cost of using makeObservable instead of makeAutoObservable (or even better getting rid of inheritance) for these ~10 classes , will be lower than the cost of introducing and maintaining any workaround.

Any usage of makeModelObservable outside of the sole purpose of extending BaseModels is unsupported. Any issues regarding non-generated usage of makeModelObservable will be closed as wontfix.

I have yet to find a case where this doesn't work. If you do, please open an issue!