Data that enters your application begins as plain, immutable data.
For example, when you dispatch a query (or mutation) the data returned will reflect the remote state of your objects.
{
user {
__typename
id
firstName
lastName
}
}
This will return:
{
"user": {
"__typename": "User",
"id": 1,
"firstName": "John",
"lastName": "Smith"
}
}
You’ll receive access to a user
object that you cannot change. You can pass this object around, and if a query or mutation causes an update that user
, it will update in-place.
mobx-depot
is the only one who can do this then that’s “okay”mobx-depot
provides a utility function called createFactory
that allows you to opt into extended functionality when you need it:
import { UserQuery, ObjectTypes } from '~/models/depot'
const userQuery = new UserQuery();
const data = await userQuery.dispatch();
const { user } = data;
// user is a plain immutable object. Let's create a factory so we can extend it
import { createFactory } from 'mobx-depot';
const EditableUser = createFactory<ObjectTypes.User>({
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
setFirstName(firstName: string) {
this.firstName = firstName;
}
});
// Adds our getter and action
const editable = EditableUser(user);
editable.setFirstName('Jeff');
editable.fullName; // -> 'Jeff Smith';
user.fullName; // TS error, otherwise `undefined`
user.firstName; // -> still 'John'
By default, whenever you pass the same object through a factory in different places, you’ll end up with the same extended object:
import { ObjectTypes } from '~/models/depot';
import { EditableUser } from '~/models/factories';
type UserPreviewProps = {
user: ObjectTypes.User;
}
const UserPreview = (props: UserPreviewProps) => {
const { user } = props;
const editable = EditableUser(user);
return (
<span>{editable.firstName}</span>
)
}
type EditUserFormProps = {
user: ObjectTypes.User;
}
const EditUserForm = (props: EditUserFormProps) => {
const { user } = props;
const editable = EditableUser(user);
return (
<input
value={editable.firstName}
onChange={e => editable.setFirstName(e.target.value)}
/>
)
}
Changes to editable
in EditUserForm
cause UserPreview
to update as well.
If this is not desirable for your use-case, you can use the branch
utility to create explicit deviations:
import { branch } from 'mobx-depot';
import { ObjectTypes } from '~/models/depot';
import { EditableMenuItem } from '~/models/factories';
type MenuPreviewProps = {
menuItems: ObjectTypes.MenuItem[];
}
const MenuPreview = (props: UserPreviewProps) => {
const { menuItems } = props;
const previewItems = menuItems.map(item => {
return EditableMenuItem(item, 'preview');
});
return (
<span>
{/* do stuff with preview items */}
</span>
)
}
export const EditableMenuItem = createFactory<ObjectTypes.MenuItem>({
setUrl() {
},
// ...
set(data) {
Object.assign(this, data);
}
})
type EditMenuItemFormProps = {
menuItem: ObjectTypes.MenuItem;
}
const EditMenuItemForm = (props: EditMenuItemFormProps) => {
const { menuItem } = props;
const editable = EditableMenuItem(menuItem, 'edit');
const commit = () => {
const preview = EditableMenuItem(menuItem, 'preview');
preview.commit(editable);
}
return (
<>
<button onClick={commit}>Done</button>
<input
value={editable.firstName}
onChange={e => editable.setFirstName(e.target.value)}
/>
</>
)
}
In this case, changes to editable
in EditUserForm
will NOT cause UserPreview
to update since they are on different branches.