mst-query #1726
-
I've written a query library for mobx-state-tree: https://github.com/ConrabOpto/mst-query/ It's sort of react-query, but with reactive models! Another inspiration is mst-gql. The main differences is that mst-query supports automatic garbage collection, and can be used with any backend. Basic query exampleimport { flow, types } from 'mobx-state-tree';
import { createQuery, MstQueryRef } from 'mst-query';
const UserModel = types.model('UserModel', {
id: types.identifier,
name: types.string,
age: types.number,
});
const MessageModel = types.model('MessageModel', {
id: types.identifier,
message: types.string,
created: types.Date,
createdBy: MstQueryRef(UserModel),
});
const getItem = ({ id }) => {
return fetch('...').then((res) => res.json());
};
const MessageQuery = createQuery('MessageQuery', {
data: MstQueryRef(MessageModel),
request: types.model({ id: types.string }),
env: types.frozen(),
}).actions((self) => ({
run: flow(function* () {
const next = yield* self.query(getItem, { id: self.request.id });
const { data, result, error } = next<typeof MessageQuery>();
}),
})); import { useQuery } from 'mst-query';
import { observer } from 'mobx-react';
import { MessageQuery } from './MessageQuery';
const MesssageView = observer((props) => {
const { id } = props;
const { data, error, isLoading } = useQuery(MessageQuery, {
request: { id },
cacheMaxAge: 300 // cache for 5 minutes
});
if (error) {
return <div>An error occured...</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return <div>{data.message}</div>;
}); Mutationimport { types } from 'mobx-state-tree';
import { createMutation } from 'mst-query';
import { MessageModel } from './models';
import { addMessage } from './api';
const AddMessageMutation = createMutation('AddMessage', {
data: MstQueryRef(MessageModel),
request: types.model({ message: types.string, userId: types.number }),
env: types.frozen(),
})
.views((self) => ({
get canRun() {
return !self.isLoading && self.request.message.length > 0;
},
}))
.actions((self) => ({
run: flow(function* () {
const next = yield* self.mutate(addMessage, self.request);
const { data } = next<typeof AddMessageMutation>();
// add new message to query
const messageList = queryCache.find(MessageListQuery);
messageList?.addMessage(data);
self.reset(); // restore request model to initial state
}),
setMessage(message: string) {
self.request.message = message;
},
})); import { useMutation } from 'mst-query';
import { observer } from 'mobx-react';
import { AddMessageMutation } from './AddMessageMutation';
const AddMessage = observer((props) => {
const [addMessage, { mutation } = useMutation(AddMessageMutation, {
request: { message: '', userId: 1 },
});
return (
<div>
<textarea
value={mutation.request.message}
onChange={ev => mutation.setMessage(ev.target.value)} />
<button
type="button"
disabled={!mutation.canRun}
onClick={() => addMessage()}>Send</button>
</div>
);
}); |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
This is great, @k-ode! Very cool -- we need more support for GraphQL in MST, and this is a great option. Feel free to add a link to your library in the documentation via a PR. |
Beta Was this translation helpful? Give feedback.
-
Just a quick update. It's now possible to use a custom root store with mst-query. This means that it's easier to use with setups more commonly used with mobx-state-tree. Here's an example: import { types, IAnyModelType, destroy } from 'mobx-state-tree';
import { ItemModel } from './ItemModel';
import { ListModel } from './ListModel';
import { UserModel } from './UserModel';
const ItemStore = types
.model({
items: types.map(ItemModel),
})
.actions((self) => ({
put(instance) {
self.items.put(instance);
},
get(id: string) {
return self.items.get(id);
},
delete(id: string) {
self.items.delete(id);
},
}));
const UserStore = types
.model({
users: types.map(UserModel),
})
.actions((self) => ({
put(instance) {
self.users.put(instance);
},
get(id: string) {
return self.users.get(id);
},
delete(id: string) {
self.users.delete(id);
},
}));
const ListStore = types
.model({
lists: types.map(ListModel),
})
.actions((self) => ({
put(instance) {
self.lists.put(instance);
},
get(id: string) {
return self.lists.get(id);
},
delete(id: string) {
self.lists.delete(id);
},
}));
const getStoreName = (typeName: string) => {
return `${typeName.replace(/Model$/, '').toLowerCase()}Store` ;
};
export const RootStore = types
.model('RootStore', {
itemStore: types.optional(ItemStore, {}),
userStore: types.optional(UserStore, {}),
listStore: types.optional(ListStore, {}),
})
.views((self) => ({
get models() {
/* This property is used for enumerating all models for garbage collection.
Excluding items from this Map is an easy way to make sure they never get gc:ed */
return new Map([
...(self.itemStore.items as any),
...self.userStore.users,
...self.listStore.lists,
]);
},
}))
.actions((self) => ({
/* put, get and delete are required actions */
put(type: IAnyModelType, _id: string, instance: any) {
self[getStoreName(type.name)].put(instance);
},
get(type: IAnyModelType, id: string) {
return self[getStoreName(type.name)].get(id);
},
delete(type: IAnyModelType, id: string, instance: any) {
self[getStoreName(type.name)].delete(id);
destroy(instance);
},
}));
// In your entry file...
import { configure } from 'mst-query';
const env = {};
configure({ env, rootStore: RootStore.create({}, env) }); |
Beta Was this translation helpful? Give feedback.
-
We've now used Creating and using a query is now a lot simpler. import { createQuery, createModelStore, createRootStore } from 'mst-query';
import { MessageModel } from './models';
const MessageListQuery = createQuery('MessageQuery', {
data: types.array(types.reference(MessageModel)),
request: types.model({ filter: '' }),
endpoint({ request }) {
return fetch(`api/messages?filter=${request.filter}`)
.then((res) => res.json());
},
});
const MessageStore = createModelStore('MessageStore', MessageModel).props({
messageListQuery: types.optional(MessageListQuery, {}),
});
const RootStore = createRootStore({
messageStore: types.optional(MessageStore, {}),
});
const queryClient = new QueryClient({ RootStore });
const { QueryClientProvider, useRootStore } = createContext(queryClient);
const MesssageList = observer((props) => {
const { messageStore } = useRootStore();
const [filter, setFilter] = useState('');
const { data, error, isLoading } = useQuery(messageStore.messageListQuery, {
request: { filter },
});
if (!data) return null;
return <div>{data.map(message => <div>{message.text}</div>)}</div>;
});
const App = () => {
return (
<QueryClientProvider>
<MessageList />
</QueryClientProvider>
);
} My next focus will be on improving docs so feel free to drop an issue if something is unclear or difficult. |
Beta Was this translation helpful? Give feedback.
This is great, @k-ode! Very cool -- we need more support for GraphQL in MST, and this is a great option. Feel free to add a link to your library in the documentation via a PR.