Reputation: 1922
I would love to be able to not define a type for a data model but still be able to have the data observable once it's loaded in. I also have relations but they could be defined statically. The docs tell me about frozen but I need the entries to be observable. Without that I would be better off sticking to what I have now.
I read something about dynamic model types in the comments of this: https://codeburst.io/the-curious-case-of-mobx-state-tree-7b4e22d461f but as I haven't worked with mst yet and don't use ts there is not enough information for me to see what solution he means exactly.
What mst wants me to do:
import React from "react";
import { render } from "react-dom";
import { types } from "mobx-state-tree";
import { observer } from "mobx-react";
const Todo = types.model({
name: types.optional(types.string, ""),
done: types.optional(types.boolean, false)
});
const eat = Todo.create({ name: "eat" });
render(
<div>
Eat TODO: {JSON.stringify(eat)}
</div>,
document.getElementById("root")
);
What I want to do:
import React from "react";
import { render } from "react-dom";
import { types } from "mobx-state-tree";
import { observer } from "mobx-react";
const Todo = types.model({});
const eat = Todo.create({ name: "eat" });
render(
<div>
Eat TODO: {JSON.stringify(eat)}
</div>,
document.getElementById("root")
);
more info:
https://github.com/mobxjs/mobx-state-tree/issues/326#issuecomment-433906949
https://github.com/mobxjs/mobx-state-tree/pull/247
Upvotes: 2
Views: 5407
Reputation: 1922
This is how it could work in the app. It works mostly, just adding and removing items does not update the component.
I came up with this, it could work. It does in the sandbox.
This is a work in progress. As is, this does not work because it's impossible to change types after initializing them.
With help of https://egghead.io/lessons/react-create-dynamic-types-and-use-type-composition-to-extract-common-functionality
https://codesandbox.io/s/m39mjomzwx
import React, { Component } from "react";
import { types } from "mobx-state-tree";
import { observer } from "mobx-react";
import { render } from "react-dom";
const ModelActions = types.model({}).actions(self => ({
addToName: function addToName(string) {
self.name = `${self.name} ${string}`;
}
}));
function createModel(instance) {
return types.compose(
types.model(instance),
ModelActions
);
}
const TodoActions = types.model({}).actions(self => ({
toggle: function toggle() {
self.done = !self.done;
}
}));
function createTodoModel(todo) {
return types.compose(
TodoActions,
createModel(todo)
);
}
function fetchUsers() {
return Promise.resolve([{ id: 1234, name: "Jan" }]);
}
function fetchTodos() {
return Promise.resolve([
{ id: 5, name: "eat", done: false },
{ id: 1, name: "drink" }
]);
}
function createDataStore(userData, todoData) {
const User = createModel(userData[0]);
const Todo = createTodoModel(todoData[0]);
return types
.model({
users: types.map(User),
todos: types.map(Todo)
})
.actions(self => {
return {
addUser(user) {
self.users[user.id] = User.create(user);
},
removeUser(id) {
self.users.delete(id);
},
addTodo(todo) {
self.todos[todo.id] = Todo.create(todo);
}
};
});
}
function makeStore([userData, todoData]) {
const store = createDataStore(userData, todoData).create();
function addData(add, data) {
for (let i in data) {
add(data[i]);
}
}
addData(store.addTodo, todoData);
addData(store.addUser, userData);
return Promise.resolve(store);
}
const AppState = types.model({ selected: false }).actions(self => {
return {
select() {
self.selected = !self.selected;
}
};
});
const appState = AppState.create();
let dataState = null;
// works
const ThingNode = observer(function ThingNode({ thing }) {
return (
<span>
{JSON.stringify(thing)}
<br />
</span>
);
});
function* getThingList(things) {
for (let key in things) {
if (Number(key)) {
yield (
<ThingNode key={key} thing={things[key]} />
);
}
}
}
// does not add or remove items
const Child = observer(function Child({ state, store, ready }) {
return (
<div>
Users:
<br />
{store ? [...getThingList(store.users)] : null}
<br />
Todos:
<br />
{store ? [...getThingList(store.todos)] : null}
<br />
Selected:
<br />
{JSON.stringify(state.selected)}
<br />
Ready:
<br />
{JSON.stringify(ready)}
</div>
);
});
@observer
class Parent extends Component {
state = { ready: null };
componentDidMount() {
Promise.all([fetchUsers(), fetchTodos()])
.then(makeStore)
.then(state => {
dataState = state;
// this.setState({ ready: true });
this.props.store.select();
})
.then(() => {
// test some stuff
dataState.addTodo({ id: 789, name: "eat a cake" });
dataState.addUser({ id: 324, name: "Henk" });
dataState.users[324].addToName("Klaassie");
dataState.todos[1].addToName("haha");
dataState.todos[5].toggle();
setTimeout(() => {
dataState.removeUser(1234);
dataState.addTodo({ id: 90, name: "dinges" });
dataState.todos[5].addToName("thing");
}, 1000);
setTimeout(() => {
dataState.todos[789].addToName("hihi");
}, 2000);
setTimeout(() => {
dataState.todos[90].addToName("twice");
}, 4000);
})
.then(() => {
this.setState({ ready: false });
})
.then(() => {
// only now do the added / removed entries become visible
setTimeout(() => this.setState({ ready: true }), 3000);
});
}
render() {
console.log("Parent", dataState);
return (
<Child
store={dataState}
state={this.props.store}
ready={this.state.ready}
/>
);
}
}
render(<Parent store={appState} />, document.getElementById("root"));
Upvotes: 3