useQuery
Simple analog of useQuery from TanStack.
Made for educational purposes.
Basic usage
Hook useQuery allows you to declarative fetching data from any source (HTTP api, GraphQL, RPC, etc.).
Just provide query function that returns Promise with data.
import { useQuery } from '@krutoo/utils/react';
function App() {
const posts = useQuery({
query() {
return fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json());
},
});
if (posts.status === 'pending') {
return 'Loading...';
}
if (posts.status === 'failure') {
return `Error: ${posts.error}`;
}
return (
<>
{posts.data.map(post => (
<div key={post.id} className='post'>
{post.title}
</div>
))}
</>
);
}
Example: declarative invalidation
You can create your own query-hook based on useQuery that accepts some payload for query. In such cases you need to invalidate your query when payload changes.
Let's see first example when your payload is simple id for some entity:
export function useUserQuery({ userId }) {
return useQuery({
// We can make key depends on `userId` to make query invalidated when userId changed
key: `user[${userid}]`,
query() {
return api.getUserById(userId);
},
});
}
In next example our payload is more complex that just single id, so we can use second argument - dependency list:
export function usePosts({ search, page, sort }) {
return useQuery(
{
key: `posts`,
query() {
return api.getPosts({ search, page, sort });
},
},
[page, search, posts], // This array works like in useEffect
);
}
Example: lazy Loading
To accumulate data in one query you can use prevData from query context.
import { useQuery } from '@krutoo/utils/react';
import { getPosts } from '#api/posts';
function usePosts() {
return useQuery({
key: 'posts',
async query(ctx) {
const prevData = ctx.prevData ?? {
items: [],
page: 1,
hasMore: true,
};
const response = await getPosts({
page: prevData.page + 1,
});
return {
items: [...prevData.items, response.items],
page: response.page,
hasMore: response.hasMore,
};
},
});
}
function App() {
const posts = usePosts();
return (
<>
{posts.data.map(item => (
<article key={item.id}>{item.title}</article>
))}
{posts.data.hasMore && <button onClick={() => posts.invalidate()}>Next page</button>}
</>
);
}
Using "Query manager"
By default all queries will stored in component state.
You can wrap your application to QueryManager implementation to control how to store, invalidate and clear queries data.
Package provides MemoryQueryManager - simple implementation of QueryManager interface that stores all data in memory.
import { createRoot } from 'react-dom/client';
import { MemoryQueryManager, QueryMangerProvider } from '@krutoo/utils/react';
import { App } from './components/app.tsx';
const manager = new MemoryQueryManager();
createRoot(document.querySelector('#root')!).render(
<QueryMangerProvider manager={manager}>
<App />
</QueryMangerProvider>,
);
TODO example of connecting useQuery to Redux through QueryMangerProvider