Query library for mobx-state-tree
- Automatic Normalization
- Garbage Collection
- Infinite Scroll + Pagination Queries
- Optimistic Mutations
- Request Argument Type Validation
- Abort Requests
- Generate Models From Graphql Schema
First, create a query...
import { createQuery, createModelStore } from 'mst-query';
const MessageQuery = createQuery('MessageQuery', {
    data: types.reference(MessageModel),
    request: types.model({ id: types.string }),
    endpoint({ request }) {
        return fetch(`messages/${request.id}`).then((res) => res.json());
    },
});...then use the query in a React component!
const MesssageView = observer((props) => {
    const { id, messageStore } = props;
    const { data, error, isLoading } = useQuery(messageStore.messageQuery, {
        request: { id },
    });
    if (error) {
        return <div>An error occured...</div>;
    }
    if (!data) {
        return <div>Loading...</div>;
    }
    return <div>{data.message}</div>;
});npm install --save mst-query mobx-state-tree
import { createModelStore, createRootStore, QueryClient, createContext } from 'mst-query';
const MessageQuery = createQuery('MessageQuery', {
    data: types.reference(MessageModel),
    request: types.model({ id: types.string }),
    endpoint({ request }) {
        return fetch(`messages/${request.id}`).then((res) => res.json());
    },
});
const MessageStore = createModelStore('MessageStore', MessageModel).props({
    messageQuery: types.optional(MessageQuery, {}),
});
const RootStore = createRootStore({
    messageStore: types.optional(MessageStore, {}),
});
const queryClient = new QueryClient({ RootStore });
const { QueryClientProvider, useRootStore } = createContext(queryClient);
function App() {
    return (
        <QueryClientProvider>
            <Messages />
        </QueryClientProvider>
    );
}import { types } from 'mobx-state-tree';
import { createQuery } from 'mst-query';
import { MessageModel } from './models';
import { getItems } from './api';
const MessageListQuery = createQuery('MessageListQuery', {
    data: types.array(types.reference(MessageModel)),
    request: types.model({ filter: '' }),
    endpoint({ request }) {
        return fetch(`messages?filter=${request.filter}`).then((res) => res.json());
    },
});import { useQuery } from 'mst-query';
import { observer } from 'mobx-react';
import { MessageQuery } from './MessageQuery';
const MesssageView = observer((props) => {
    const { id, snapshot, result } = props;
    const rootStore = useRootStore();
    const {
        data,
        error,
        isLoading,
        isFetched,
        isRefetching,
        isFetchingMore,
        query,
        refetch,
        cachedAt,
    } = useQuery(rootStore.messageStore.messageQuery, {
        data: snapshot,
        request: { id },
        enabled: !!id,
        onError(data, self) {},
        staleTime: 0,
    });
    if (error) {
        return <div>An error occured...</div>;
    }
    if (isLoading) {
        return <div>Loading...</div>;
    }
    return <div>{data.message}</div>;
});import { types } from 'mobx-state-tree';
import { createInfiniteQuery, RequestModel } from 'mst-query';
import { MessageModel } from './models';
const MessagesQuery = createInfiniteQuery('MessagesQuery', {
    data: types.model({ items: types.array(types.reference(MessageModel)) }),
    pagination: types.model({ offset: types.number, limit: types.number }),
    endpoint({ request }) {
        return fetch(`messages?offset=${request.offset}&limit=${request.limit}`).then((res) =>
            res.json()
        );
    },
});
const MessageStore = createModelStore('MessageStore', MessageModel).props({
    messagesQuery: types.optional(MessagesQuery, {}),
});import { useInfiniteQuery } from 'mst-query';
import { observer } from 'mobx-react';
import { MessageListQuery } from './MessageListQuery';
const MesssageListView = observer((props) => {
    const [offset, setOffset] = useState(0);
    const { data, isFetchingMore, query } = useInfiniteQuery(messageStore.messagesQuery, {
        request: { filter: '' },
        pagination: { offset, limit: 20 },
    });
    if (isFetchingMore) {
        return <div>Is fetching more results...</div>;
    }
    return (
        <div>
            {data.items.map((item) => (
                <Message />
            ))}
            <button onClick={() => setOffset(data.items.length)}>Get more messages</button>
        </div>
    );
});import { types } from 'mobx-state-tree';
import { createMutation } from 'mst-query';
const AddMessageMutation = createMutation('AddMessage', {
    data: types.reference(MessageModel),
    request: types.model({ message: types.string }),
});
const MessageStore = createModelStore('MessageStore', MessageModel)
    .props({
        messagesQuery: types.optional(MessagesQuery, {}),
        addMessageMutation: types.optional(AddMessageMutation, {}),
    })
    .actions((self) => ({
        afterCreate() {
            onMutate(self.addMessageMutation, (data) => {
                self.messagesQuery.data?.items.push(data);
            });
        },
    }));import { useMutation } from 'mst-query';
import { observer } from 'mobx-react';
import { AddMessageMutation } from './AddMessageMutation';
const AddMessage = observer((props) => {
    const { messageStore } = props;
    const [message, setMessage] = useState('');
    const [addMessage, { isLoading }] = useMutation(messageStore.addMessageMutation);
    return (
        <div>
            <textarea value={message} onChange={(ev) => setMessage(ev.target.value)} />
            <button
                type="button"
                disabled={!message.length || isLoading}
                onClick={() => {
                    addMessage({
                        request: { message },
                        optimisticUpdate() {
                            return {
                                id: 'temp' + Math.random(),
                                message,
                            };
                        },
                    });
                    setMessage('');
                }}>
                Send
            </button>
        </div>
    );
});Generate mobx-state-tree models from your graphql schema.
npx mst-query-generator schema.graphqlThe option staleTime controls how much time should pass before a cached value needs to be refetched from the server.
rootStore.runGc();const queriesWithId = rootStore.getQueries(MessageQuery, (q) => q.request.id === 'message-id');
queriesWithId.forEach((q) => q.refetch());
const allMessageMutations = rootStore.getQueries(UpdateMessageMutation);
allMessageMutations.forEach((m) => m.abort());