Reputation: 101
I'm trying to use React-Query with Astro to fetch data from my Django Rest Framework backend. Astro has been a great way to organize my react-based frontend but I am worried it might not be compatible with React-Query.
Whenever I try to make a query to my backend I get an 'isLoading' value of true (and an isError of false). I never manage to recover the data from my endpoints however.
I have been following a variety of tutorials with the same results. Here is the code where I'm stuck:
import { QueryClient, useQueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { gettestApi } from "../../api/testApi";
function MyComponent(props) {
const queryClient = useQueryClient();
const {
isLoading,
isError,
error,
data: test
} = useQuery('test', gettestApi)
let content
if (isLoading) {
content = <p>Loading...</p>
} else if (isError){
content = <p>{error.message}</p>
} else {
content = JSON.stringify(test)
}
As you can see, I import an axios function from /api/testAPI.js which looks like this:
import axios from "axios"
const testApi = axios.create({
baseURL: "http://127.0.0.1:8000"
})
export const gettestApi = async () => {
return await testApi.get("/api/endpoint/").then(response => response.data)
}
That's how most tutorials I have seen and the official documentation wrap up their examples, however my backend server which should be triggered by this endpoint records absolutely no hits from react-query, which is curious to me. I understand that nothing 'calls' my react-query or my gettestApi() function, but it seems to be unnecessary for other people to retrieve their data.
Maybe it would be useful to point out that contrary to other framework with React, Astro does not have an App.js root to surround with
<QueryClientProvider client={client}>
<App />
</QueryClientProvider>
Instead, I have added these QueryClientProvider brackets to the highest React component I could.
I feel like I'm missing some intuition about Tanstack Query/ React-Query. Could anybody point me in the right direction? Thanks a lot for the help.
Upvotes: 1
Views: 5944
Reputation: 1584
I was researching this too and found the following...
Check out https://www.youtube.com/watch?v=3Iz3D7cVi04 "Shared State Between Multiple Frameworks"
Description:
TanStack Query maintainer Aryan Deora will show us how he does it with Query + Astro.
At 00:29 they cover react-query specifically. Around 00:33-00:34 they discuss react-query v5 vs v4.
Repo (in the repo take note of the branches): https://github.com/ardeora/astro-tanstack-lwj
The video is important to understand the code in proper context as they work through different cases and examples, adding/removing + commenting/uncommenting parts, etc.
Per @braden-wong comment with "TL;DR" below you can pass react-query client e.g. queryClient
as a second parameter to useQuery
.
This can help you get working with Tanstack Query and Astro quickly however note there are many nuances if you choose to use this approach.
There is no universal answer to support Tanstack Query across any Astro project because Astro is flexible and supports many wildly different project configurations.
If you are using Astro to output distinct html pages (a common default) then it is likely your client code is creating a new instance of the query client — and therefore a blank query cache — with each page load.
Tanstack Query's caching mechanism is often the very reason developers choose to use it in the first place.
One way to potentially sidestep this issue is if you load an entire multi-view / multi-"page" SPA within a given Astro island on a given page.
If you use this approach you can even use a QueryClientProvider
in the root component of the SPA/island provided that you ensure it is loaded using a client:*
directive.
React Context works within Astro Islands but not between them.
To support using Tanstack Query across multiple pages / page loads —
Tanstack Query supports a "persist" feature that can use features such as local storage or Indexed DB to store the query client and cache so it can be restored on new page loads.
Take extra care with persistence and hydration of the query client/cache on projects that use server rendering (SSR).
Astro View Transitions are an optional feature that can impact Tanstack Query and the query client.
transition:persist
vs. transition:persist-props
Upvotes: 7
Reputation: 119
Using React, you can add the Client directly as a parameter inside the useQuery() function and you don't need to wrap your entire app with any provider. Here's an example:
import { QueryClient } from "@tanstack/query-core";
import { useQuery } from "@tanstack/react-query";
export const CLIENT = new QueryClient();
// fetch function
async function getTodos() {
const url = "http://example.com/todos";
const response = await fetch(url);
return await response.json();
}
// custom hook
export const useTodosQuery = () => {
return useQuery(
{
queryKey: ["my-todo-list"],
queryFn: getTodos,
},
CLIENT
);
};
Upvotes: 2
Reputation: 4942
If you don't need to share data between islands, you can just wrap your React component with a QueryClientProvider
like so:
import {
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
function withQueryClient<P extends React.HTMLAttributes<unknown>>(
Component: React.ComponentType<P>,
) {
return (props: P) => (
<QueryClientProvider client={new QueryClient()}>
<Component {...props} />
</QueryClientProvider>
);
}
const MyComponent = withQueryClient<{}>((props) => {
return (
<>
</>
);
});
Upvotes: 1
Reputation: 11
You can create an HOC that can return a new component that has a QueryClientProvider
wrapper on the existing component that's using react-query
HOC:
export const QueryProviderHoc = (Comp: React.FC) => {
const newComp = () => {
return (
// this is your QueryClientProvider
<QueryProvider>
// Component that is using react-query
<Comp />
</QueryProvider>
)
}
return newComp
}
Component with react-query:
const Items = () => {
// some custom react-query hook
const {data, isLoading} = useGetItems()
if(!data || isLoading)
{
return <p className="text-center">Loading...</p>
}
return (
<>
<ItemList data={data}/>
</>
)
}
export default QueryProviderHoc(Items)
On Astro Page/file:
<Layout title="dashboard">
<p>Hello world</p>
<Items client:load />
</Layout>
Upvotes: 1
Reputation: 185
You can set client:only on your Astro component so the React component doesn't run on the server. There are shared-state limitations but still React Query feels better than just fetch + useEffect + own-code even if its not in a complete React app. In this example I'm also using an init function that reads cookies from the client's browser which is another case for when to use client:only.
Astro:
---
import Layout from "../../layouts/Layout.astro";
import ClientPanel from "../../components/client/ClientPanel";
---
<Layout title={ 'Client' }>
<ClientPanel client:only></ClientPanel>
</Layout>
React:
// imports
const queryClient = new QueryClient()
/** client:only component */
const ClientPanel = () => (
<QueryClientProvider client={queryClient}>
<ClientData />
</QueryClientProvider>
)
const ClientData = () => {
const { getUser, getSession } = useSession(); // read cookies functions
const [ user, setUser ] = useState(getUser);
const { isLoading, error, data } = useQuery({
queryKey: ['patientData'],
queryFn: () => getSession() // validate or refresh token
.then(session => fetchPatientData(session.tokens.token))
.catch(error => error === 'INVALID_SESSION' ? null : undefined)
})
if (!user || data === null) window.location.replace('/login')
// return statement, etc.
Upvotes: 2
Reputation: 28948
From what I've seen in the astro docs:
The most important thing to know about Astro components is that they render to HTML during your build. Even if you run JavaScript code inside of your components, it will all run ahead of time, stripped from the final page that you send to your users. The result is a faster site, with zero JavaScript footprint added by default.
So it seems all react code only runs on the server, where data fetching via useEffect
or useSyncExternalStore
subscriptions just doesn't run. But this is exactly what react-query is doing, so yeah I think they don't work well together. I'm also not sure what the purpose of react-query in a setting would be where there is no client side javascript.
Upvotes: 1