Why is useQuery in apollo client not actually getting the data? - reactjs

I am having strange situation with React routing with GraphQL. I have a users in /users url and when i go to users page for the first time useQuery is working, request is going to the server and bringing me some data. Then, if i go to another page and come back to the users page, useQuery is not working and request is not going to the server. useQuery is working and request is going to the server when i reload the page. Here is my code and query for Users component:
Users.js
import React, { useState, useEffect } from "react";
import { useLazyQuery, useQuery } from '#apollo/client'
import { USERS} from './queries'
const [usersList, setUsersList] = useState([])
const { loading, error, data } = useQuery(USERS);
const Users= () => {
useEffect(() => {
const result = data?.users?.payload.filter(item => item.status === "waiting" && !item.isDeleted)
setUsersList(result)
}, [loading, data])
return (
<div>
{usersList?.map(user => (
<div key={user.key}>
<h1>{user.name}</h1>
<p>{user.status}</p>
</div>
))}
</div>
)
}
queries.js
import { gql } from '#apollo/client';
export const USERS= gql`
query{
users(size: 50){
payload{
key,
isDeleted,
status,
name,
...
}
}
}`
So, final question, why useQuery is working only one time when site is reloaded, not rerendered?

This is by design –– your query's results would have been stored in Apollo Client's cache the first time that query triggered. If the query's variables haven't changed, then the query won't actually travel the network again, Apollo Client will pull the data out of its cache instead.
You could change the fetchPolicy on your useQuery hook if you want the request to go over the network to your server every time your hook is run: https://www.apollographql.com/docs/react/data/queries/#supported-fetch-policies

You can use useLazyQuery and useEffect for that
useLazyQuery
const [runQuery, { data, loading, called }] = useLazyQuery(yourQuery);
useEffect(() => { runQuery(); }, [location]);
I think you will get the idea ;)
EDIT: also you can change FetchPolicy on the normal to network-only query (I think this should work too):
params of useQuery

Related

How do I make Next.js 13 server-side components in the app directory that depend on useEffect for props?

I'm trying to write a Next.js 13 newsletter page in the app directory that uses server-side components that depend on useEffect for props. The useEffect fetches data from a REST API to get newsletters which will render the content of the page. The code I'm using is below. I'm having trouble figuring out how to configure the server-side components to work when I need to "use client" for interactivity. How can I make sure that the server-side components are rendered before it is sent to the client?
Code:
import Navbar from '#/components/navbar'
import Footer from '#/components/footer'
import Pagination from './pagination'
import IssueCards from './issueCards';
import { useState, useEffect } from 'react';
import axios from 'axios';
const Newsletters = () => {
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [issuesPerPage, setIssuesPerPage] = useState(5);
useEffect(() => {
const fetchIssue = async () => {
const res = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_API}/newsletters`)
setIssues(res.data)
}
fetchIssue()
}, [])
// Change page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
const indexOfLastIssue = currentPage * issuesPerPage;
const indexOfFirstIssue = indexOfLastIssue - issuesPerPage;
const currentIssues = issues.slice(indexOfFirstIssue, indexOfLastIssue)
return (
<>
<Navbar />
<div className="newsletter-container" id='newsletter-container'>
<h1>Newsletters</h1>
<hr></hr>
<div className="newsletter-wrapper">
<IssueCards issues={currentIssues} />
<Pagination
issuesPerPage={issuesPerPage}
totalIssues={issues.length}
paginate={paginate}
/>
</div>
</div>
<Footer />
</>
);
}
export default Newsletters;
How do I configure Next.js 13 server-side components that depend on useEffect for props and ensure that the content is rendered before it is sent to the client?
I tried following the Nextjs docs on Server and Client components but I am unsure of how I can pass down the props information onto the server.
Unfortunately, server components don't allow for hooks such as useEffect, see documentation here.
You have two main options:
New way of fetching data
Server components allow for a new way of fetching data in a component, described here.
This approach would look something this:
async function getData() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
// Recommendation: handle errors
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return <main></main>;
}
Revert to client components
Your other option is to use the use client directive at the top of your file and leaving Newsletter as a client component. Of course, this way, you wouldn't get the benefits of server components, but this would prevent you from having to change your code substantially. Also, keep in mind that server components are still in beta.

React Query does not work with Astro Framework

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.
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.
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.

How to use the useQueries hook in react-query v4

So I'm perfectly able to retrieve matches from a competition with the Football-data.org API and display them in my react/typescript application using the useQuery hook from react-query:
import {CompetitionProps} from "../App";
import {getMatchesFromApi, Match} from "../api/GetMatchesFromApi";
import {List, ListItem} from "#mui/material";
import {useQuery} from "#tanstack/react-query";
function MatchesList({competitions}: CompetitionProps) {
const { isLoading, error, data, isFetching} = useQuery(["matches"], async () => {
return await getMatchesFromApi(competitions);
});
if (isLoading || isFetching) {
return (<div>Loading</div>);
} else {
return (
<List>
{data?.map((match: Match) => {
return (
<ListItem key={match.id}>{match.homeTeam.shortName} - {match.awayTeam.shortName}</ListItem>
);
})}
</List>
);
}
}
export default MatchesList;
However I want all matches from a list of competitions (the competitions can be different based on user preferences). When reading the docs of react-query, the useQueries hook should do the trick. These docs unfortunately don't show how to handle the results of the useQueries hook:
https://tanstack.com/query/v4/docs/reference/useQueries
I tried to use it like this:
import {
useQueries,
UseQueryOptions,
UseQueryResult
} from "#tanstack/react-query";
import { getMatchesFromApi, Match } from "./GetMatchesFromApi";
const allCompetitions = [2003, 2021];
function MatchesList() {
const results = useQueries({
queries: allCompetitions.map<UseQueryOptions<Match[]>>(
(competition: number) => {
return {
queryKey: ["competition", competition],
queryFn: async () => await getMatchesFromApi(competition)
};
}
)
});
return <div>{results.length}</div>;
}
export default MatchesList;
Even though I'm not even attempting to display the data yet, just using this code that only prints the length of the results array, will cause the code to fetch every few seconds. This will result in 429 (too many requests) responses of the football-data.org API fairly quickly.
This behavior doesn't match at all with the default staleTime and cacheTime settings explained in:
https://medium.com/doctolib/react-query-cachetime-vs-staletime-ec74defc483e
The question: How do I stop this infinite fetching loop? The reason I wanted to use react-query at all is to lazy fetch the match data only once.
Full project to reproduce this problem:
https://codesandbox.io/s/serene-raman-47n2uz
(If you want to reproduce it, you'll have to register on football-data.org for free and generate an API key. Put your key in the Key.ts file. I don't want to put mine on the internet).
Thanks!

How to refresh graphql data on state change

import { useQuery, gql, useMutation } from "#apollo/client";
const Questions = () => {
const [modal, setModal] = useState(false)
const QUESTION_QUERIES = gql`
query getQuestions(
$subjectRef: ID
$gradeRef: ID
$chapterRef: ID
$status: String
) {
getQuestions(
subjectRef: $subjectRef
gradeRef: $gradeRef
chapterRef: $chapterRef
status: $status
) {
id
question_info
question_type
answer
level
published
subjectRef
gradeRef
chapterRef
levelRef
streamRef
curriculumRef
options
status
subject
grade
chapter
stream
curriculum
}
}
`;
const { loading, error, data } = useQuery(QUESTION_QUERIES);
return (
<div>
</div>
)
}
Here is my react graphql code.
I wants to fetch data when modal change using state if modal status change to true to false or false to
true it will make api call to fetch questions again
Please take a look how to solve the issue.
use useLazyQuery:
const [updateFn,{ loading, error, data }]= useLazyQuery(QUESTION_QUERIES);.
Then create useEffect with modal as dependency variable, and call updateFn inside useEffect
You want to fetch data after the modal state change, So you simply use useEffect and put modal in the dependency list of the useEffect and for useQuery there is also a function called refetch, the logic would be like this
const { loading, error, data, refetch } = useQuery(QUESTION_QUERIES);
useEffect(() => {
// the reason I put if condition here is that this useEffect will
// also run after the first rendering screen so you need to put a check
// to do not run refetch in that condition
if (data) refetch();
}, [modal]);

Apollo and React: refreshQueries not working in mutation fired from onClick()

I'm building a simple shopping app. On load, we'll make a query to check whether a cart exists. For now, that query always returns null (I haven't implemented logic yet to check whether a user has a shopping cart already). Then, when a user clicks "create cart", we do a mutation to create it. Finally, we'll use refreshQueries after the mutation finishes to fetch the cart (and its products) by ID, which is returned from the mutation. We then render those products in a pure component.
The problem I'm encountering is that nothing re-renders after users click the button and refreshQueries happens. I know the query is being sent and returning a created cart with products by looking at my developer tools' network tab. It's just that Apollo doesn't seem to notice the change.
Mongo is my back-end.
Here's the relevant code:
// query
import { gql } from 'react-apollo';
export const cartQuery = gql`
query CartQuery($cartId: ID) {
cart(cartId: $cartId) {
_id,
products {
_id,
name
}
}
}
`;
// mutation
import { gql } from 'react-apollo';
export const createCartMutation = gql`
mutation CreateCartMutation {
createCart {
_id
}
}
`;
// Apollo + React stuff
import React from 'react';
import { graphql } from 'react-apollo';
import { createCartMutation } from '../../mutations';
import { cartQuery } from '../../queries';
const BuildCart = ({ mutate }) => (
<button
onClick={() => {
// even when I hardcode the ID of a cart that exists and contains products, it doesn't matter
mutate({
refetchQueries: [{ query: cartQuery, variables: { cartId: '12345abcd' } }],
});
}}
>
Click
</button>
);
const BuildCartConnected = graphql(createCartMutation)(BuildCart);
const Cart = ({ data, data: { cart, loading } }) => {
console.log(cart); // always null, even after clicking button
return (
<div>
<BuildCartConnected />
{loading && <p>Loading...</p>}
// never gets to this step, even though the response is a properly formed cart with an array of products
{cart && cart.products.map(product => (
<p key={Math.random()}>{product.name}</p>
))}
</div>
);
};
const CartConnected = graphql(cartQuery)(Cart);
export default CartConnected;
If it's helpful, here's what the response from refetchQueries looks like in the network tab:
{"data":{"cart":{"_id":"12345abcd","products":[{"_id":"abcdef12345","name":"Product A","__typename":"Product"}],"__typename":"Cart"}}}
Your cart query takes a cartId variable. In the code above, you're not providing that variable, so its value is undefined. Apollo associates that combination of query plus variable(s) with your Cart component.
When you call refetch queries with a different variable than what is provided to the HOC, the results of that new query are fetched and persisted in the store. However, as far as Apollo knows, you still want the results of a query with undefined as the variable value for that component, not this new result you had it fetch.
refetchQueries shouldn't be utilized for what you're trying to do. Instead, the variable for the cart query should be derived from props:
const options = ({cartId}) => ({variables: {cartId}})
const CartConnected = graphql(cartQuery, {options})(Cart)
Then, when you call mutate, it returns a Promise that will resolve to the data returned by the mutation. We can grab the cartId from the response and store it within application state (by calling setState, firing off a Redux action, etc.). You can then pass that state down to your Cart component as a prop. When the state is updated, the prop changes and the query with the newly provided variable is fetched.

Resources