# mst-query

> Query library for mobx-state-tree

Latest version **4.8.2** (published 2026-09-10) · MIT license · 0 weekly downloads

## Install

```sh
npm install mst-query
pnpm add mst-query
yarn add mst-query
bun add mst-query
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 4.8.2 |
| Published | 2026-09-10 |
| First published | 2021-04-22 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 1 |
| Unpacked size | 311.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 125 |
| Author | Conrab Opto |
| Maintainers | kimgronqvist, dsnn |

## Links

- npm: https://www.npmjs.com/package/mst-query
- Repository: https://github.com/ConrabOpto/mst-query
- Homepage: https://github.com/ConrabOpto/mst-query#readme
- Issues: https://github.com/ConrabOpto/mst-query/issues
- npm.io page: https://npm.io/package/mst-query

## Dependencies (1)

- [@wry/equality](https://npm.io/package/@wry/equality.md) 0.5.7

## Recent versions

- 4.8.2 (latest) — 2026-09-10
- 3.0.0-beta.0 (beta) — 2023-03-31
- 4.8.1 — 2026-09-09
- 4.8.0 — 2026-09-03
- 4.7.0 — 2026-06-15
- 4.6.0 — 2026-05-29
- 4.5.0 — 2026-05-05
- 4.4.1 — 2026-01-12
- 4.4.0 — 2026-01-05
- 4.3.0 — 2025-10-06
- 4.2.1 — 2025-04-28
- 4.2.0 — 2025-04-16
- 4.1.2 — 2025-04-08
- 4.1.1 — 2025-03-18
- 4.1.0 — 2025-03-17
- … 141 more at https://npm.io/package/mst-query/versions

## README

Query library for mobx-state-tree

<a href="https://github.com/ConrabOpto/mst-query/actions/workflows/unit-tests.yml">
<img src="https://github.com/ConrabOpto/mst-query/actions/workflows/unit-tests.yml/badge.svg" />
</a><a href="https://bundlephobia.com/package/mst-query" target="\_parent">
  <img alt="" src="https://badgen.net/bundlephobia/minzip/mst-query@latest" />
</a>

# Features

-   Automatic Normalization
-   Garbage Collection
-   Infinite Scroll + Pagination Queries
-   Optimistic Mutations
-   Request Argument Type Validation
-   Abort Requests
-   Generate Models From Graphql Schema

# Examples

-   [Basic](https://codesandbox.io/p/devbox/mst-query-basic-example-nk49ds?file=%2Fsrc%2Findex.tsx)
-   [Table Filters](https://codesandbox.io/p/devbox/mst-query-table-filters-example-2j3h3v?file=%2Fsrc%2Findex.tsx%3A18%2C26)

# Basic Usage

First, create a query...

```ts
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!

```tsx
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>;
});
```

# Documentation

## Installation

```
npm install --save mst-query mobx-state-tree
```

## Configuration

```tsx
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>
    );
}
```

## Queries

### `createQuery`

```tsx
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());
    },
});
```

### `useQuery`

```tsx
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>;
});
```

## Paginated and infinite lists

```tsx
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, {}),
});
```

```tsx
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>
    );
});
```

## Mutations

### `createMutation`

```tsx
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);
            });
        },
    }));
```

### `useMutation`

```tsx
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>
    );
});
```

## Model generator (GraphQL)

Generate mobx-state-tree models from your graphql schema.

```ts
npx mst-query-generator schema.graphql
```

## Cache

The option `staleTime` controls how much time should pass before a cached value needs to be refetched from the server.

### Garbage collection

```tsx
rootStore.runGc();
```

### Globally interacting with queries

```tsx
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());
```

---
_Source: https://npm.io/package/mst-query · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
