mst-query v3.4.0-canary.8
Query library for mobx-state-tree
Features
- Automatic Normalization
- Garbage Collection
- Use with any backend (REST, GraphQL, whatever!)
- Optimistic UI with automatic rollback
- Infinite Scroll + Pagination Queries
- Request Argument Validation
- Abort Requests
- Client Models
Basic Usage
First, create some models, a query and a store...
import { types } from 'mobx-state-tree';
import { createQuery, MstQueryRef, createRootStore, createModelStore } 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 MessageQuery = createQuery('MessageQuery', {
data: MstQueryRef(MessageModel),
request: types.model({ id: types.string }),
endpoint: ({ request }) => {
const { id } = request;
return fetch(`/api/message/${id}`).then((res) => res.json());
}
});
const RootStore = createRootStore({
messageStore: types.optional(createModelStore(MessageModel), {}),
userStore: types.optional(createModelStore(UserModel), {}),
}).props({
messageQuery: types.optional(MessageQuery, {})
}).actions(self => ({
getMessage: flow(function* (request) {
self.messageQuery.run({ request });
})
}));
...then use the query in a React component!
import { useQuery } from 'mst-query';
import { observer } from 'mobx-react';
const queryClient = new QueryClient({ RootStore });
const { QueryClientProvider, useRootStore } = createContext(queryClient);
const useMessageQuery = (id: string) => {
const rootStore = useRootStore();
return useQuery(rootStore.messageQuery, rootStore.getMessage {
request: { id },
});
};
const MesssageView = observer((props) => {
const { id } = useParams();
const { data, error, isLoading } = useMessageQuery(id);
if (error) {
return <div>An error occured...</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return <div>{data.message}</div>;
});
const App = observer((props) => {
return (
<QueryClientProvider>
<MessageView id={props.id} />
</QueryClientProvider>
);
});
Installation
npm install --save mst-query mobx-state-tree
Models
In general, models can be created as usual. The main difference is how we handle references.
MstQueryRef
A custom reference that replaces types.reference
.
import { types } from 'mobx-state-tree';
const UserModel = types.model({
id: types.identifier, // a normal identifier
name: types.string.
age: types.number
});
const MessageModel = types.model({
message: types.string,
createdBy: MstQueryRef(UserModel)
});
Since data can be garbage collected in mst-query, MstQueryRef
doesn't throw if it cannot find a suitable model in the internal cache. Instead, it simply returns the id as a string, allowing us to fetch data for this model again.
Queries
createQuery
A query is just a mobx-state-tree model, but with special properties, called a QueryModel
. Here's an example of a query that fetches a list of messages.
import { types } from 'mobx-state-tree';
import { createQuery } from 'mst-query';
import { MessageModel } from './models';
const MessageListQuery = createQuery('MessageListQuery', {
data: types.array(MstQueryRef(MessageModel)),
request: types.model({ filter: '' }),
});
The first argument to createQuery
is the name of this query. The second is an option object that controls how this query recevies (data) and transmits (request) data.
TODO: endpoint
useQuery
import { useQuery } from 'mst-query';
import { observer } from 'mobx-react';
import { MessageQuery } from './MessageQuery';
const MesssageView = observer((props) => {
const { id, initialData, result } = props;
const {
data,
error,
isLoading,
isFetched,
isRefetching,
isFetchingMore,
query,
refetch,
cachedAt,
} = useQuery(MessageQuery, {
request: { id },
enabled: true,
initialData: initialData,
onFetched(data, self) {},
onSuccess(data, self) {},
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
useInfiniteQuery
import { useInfiniteQuery } from 'mst-query';
import { observer } from 'mobx-react';
import { MessageQuery } from './MessageQuery';
const MesssageView = observer((props) => {
const { id, cachedData } = props;
const { data, error, isLoading, query } = useInfiniteQuery(MessageQuery, {
request: { id },
});
useEffect(() => {
query.run();
}, []);
if (error) {
return <div>An error occured...</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return <div>{data.message}</div>;
});
queryMore
// TODO: Example
The difference between query
and queryMore
is that the latter does not automatically merge it's result to the underlying query. This allows you to easily control how the data is appended to your list. It also means mst-query supports many different forms of pagination (offset-based, cursor-based, page-number-based) out of the box.
Mutations
createMutation
// TODO: Example
useMutation
// TODO: Example
Optimistic updates
// TODO: Example, write docs
Client models
Cache
// TODO: Write docs
rootStore
10 days ago
19 days ago
20 days ago
20 days ago
1 month ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
7 months ago
7 months ago
7 months ago
8 months ago
7 months ago
8 months ago
8 months ago
8 months ago
8 months ago
11 months ago
11 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago