I have mutation as follows:
<Mutation
mutation={ADD_NEW_SLOT}
refetchQueries={() => [{ query: GET_COMPANY_ADDRESSES, variables: { companyId: this.props.session.company.id } }]}
awaitRefetchQueries={true}
>
.......
</Mutation>
Where GET_COMPANY_ADDRESSES is exported from a parent component.
But it doesn't refresh after the mutation is done.
What am I doing wrong?
UPDATE
The return of the render function in the parent component is as follows:
<Query query={GET_COMPANY_ADDRESSES} variables={{companyId: session.company.id}} notifyOnNetworkStatusChange={true} fetchPolicy={'cache-and-network'}>
{({loading, error, refetch, data}) => {
if (loading) return <LoadingIndicator/>;
if (error) return <ErrorIndicator description={error.message}/>;
const treeNodes = convertSlotsToTree(data);
const address = data.companyAddresses[1];
return (
<AddSlot address={address}
toggleSlotForm={this.props.togglePanel}
session={this.props.session}/>
)
}}
</Query>
The graphql query is in the same file and it is as follows:
export const GET_COMPANY_ADDRESSES = gql`
query CompanyAddresses($companyId: Int!) {
companyAddresses(companyId: $companyId) {
id
name
default
compound
address {
id
addressFull
countryCode
city
postCode
slotSet{
id
area
zone
aisle
side
level
position
disabled
col
printEntry
fullName
}
}
}
}
`;
It still does not work with react-apollo 3.1.x w/o workaround.
It seems that if you use these two things, updates will be sent to Query:
Set option for Query: fetchPolicy="cache-and-network" (you use this already).
Comment refetchQueries and use Query's refetch instead. Attach function to Mutation's onCompleted, and call refetch from the Query directly. You should see graphql query in HTTP requests, and UI should updates itself as well.
Note. "cache-first" as fetchPolicy did not work, although I guess cache is updated, but UI does not.
With Angular I found this working, as expected (note the "no-cache" flag):
this.postsQuery = this.apollo.watchQuery<any>({
query: GET_USER_ON_ROLE,
fetchPolicy: "no-cache",
variables: {
roleId : this.roleId
}
});
this.querySubscription = this.postsQuery
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.allUsers = data.getAllUsersPerRoleUI;
});
An you need to reload once the mutation is over using refetch():
public reloadFunction()
{
this.postsQuery.refetch()
}
Related
I'm new to React Query and am wondering what that's about--forestalling execution of my GraphQL query until I click anywhere within the browser's viewport. Shouldn't the query just execute straight away?
Here's my code:
import { useUrlHashParameters } from "../src/hooks";
import { useQuery } from "react-query";
import { gql, request } from "graphql-request";
export default function Home() {
const parameters = useUrlHashParameters();
const { data } = useQuery("test", async () => {
if (parameters) {
localStorage.setItem("accessToken", parameters.accessToken);
const url = "http://localhost:3001/graphql";
const document = gql`
query {
products {
id
title
description
totalInventory
__typename
}
}
`;
const requestHeaders = {
Authorization: `Bearer ${localStorage.getItem("accessToken")}`,
};
return await request({
url,
document,
requestHeaders,
});
}
});
console.log(data);
return (
<div className={styles.container}>
<main className={styles.main}>
<Image
src="/blah-logo.png"
alt="Blah"
width={300}
height={75}
/>
{data?.products.map(({ title }, index: number) => (
<div key={index}>{title}</div>
))}
</main>
</div>
);
}
Shouldn't the query just execute straight away?
Yes, it should, and it likely does. My best guess is that on the first render, the parameters are undefined, and since you check for if (parameters) in the queryFn without doing anything in an else branch, the queryFn will just return a Promise that resolves to undefined (which will be illegal in v4 btw).
Then, when you focus the window, you get a refetch, in which case parameters likely exist.
You can verify this by putting a log statement into the queryFn, but outside the if statement.
Further, all parameters that your query uses should be part of the query key. That makes sure that you get automatic refetches when the parameters are changing. So changing your query key to:
["test", parameters]
and setting: enabled: !!parameters instead of the if to stop the query from running if there are no parameters is likely the best course of action:
const parameters = useUrlHashParameters();
const { data } = useQuery(
["test", parameters],
() => request({...}),
{
enabled: !!parameters
}
)
i have a homepage (/home) with a list of products as cards (retrieved
via useQuery) each of which has an upvote button
when I click upvote,
i trigger a mutation to upvote + a UI change to update the vote
count
when i go to another page, and then go back to /home,
useQuery doesn’t retrieve the products with the correct vote count
however, when I check my DB, the products all have the correct vote
count.
Why doesuseQuery not return the right amount until i do another page
refresh?
for reference, here it is below:
const Home = props => {
const {data, loading, error} = useQuery(GET_PRODUCTS_LOGGED_IN, {
variables: {
userid: props.userid
}
});
console.log(
'data', data.products // this data is outdated after I go from /home -> /profile -> /home
);
return (
<Container>
{_.map(data.products, product => (
<VoteButton
hasVoted={product.hasVoted}
likes={product.likes}
productid={product.productid}
/>
))}
</Container>
);
}
const VoteButton = ({likes, hasVoted, productid}) => {
const [localHasVoted, updateLocalHasVoted] = useState(hasVoted);
const [likesCount, updateLikesCount] = useState(likes);
const [onVote] = useMutation(VOTE_PRODUCT);
const onClickUpvote = (event) => {
onVote({
variables: {
productid
}
})
updateLocalHasVoted(!localHasVoted);
updateLikesCount(localHasVoted ? likesCount - 1 : likesCount + 1);
}
return (
<VoteContainer onClick={onClickUpvote}>
<VoteCount >{likesCount}</VoteCount>
</VoteContainer>
);
};
On your useQuery call, you can actually pass it a config option called 'fetch-policy' which tells Apollo how you want the query to execute between making the call or using the cache. You can find more information here, Apollo fetch policy options.
A quick solution would be be setting fetch-policy to cache and network like the the example below.
const {data, loading, error} = useQuery(GET_PRODUCTS_LOGGED_IN, {
variables: {
userid: props.userid
},
fetchPolicy: 'cache-and-network',
});
You can also make it so that when your mutation happens, it will run your query again by setting the 'refetch-queries' option on useMutation like the code below.
This will cause your query to trigger right after the mutation happens.
You can read more about it here Apollo mutation options
const [onVote] = useMutation(VOTE_PRODUCT, {
refetchQueries: [ {query: GET_PRODUCTS_LOGGED_IN } ],
});
Suppose data - is data from a parent query.
Child react-component:
const ShowDetails = ({data}) => {
const { loading, error, data_details } = useQuery(someQueryAsksAdditionalFileldsForEntryAlreadyPresentInCache);
}
someQueryAsksAdditionalFileldsForEntryAlreadyPresentInCache -- asks for additional fields that are missing in data.
When (!loading && !error) data_details will have requested fields.
Issue: data_details will have only requested fields.
Question: Is there a way to use parent data with merged-additional-requested fields in ShowDetails and ignore data_details?
In Chrome with help of Apollo devtools I see that apollo-cache has one entry from merged data and data_details.
I do not want to re-fetch all existed entries in data.
Example:
Parent component query:
const bookQuery = gql`
query ($bookId: ID!) {
book(id: $bookId) {
id
author
}
}
`
Details query:
const bookEditionsQuery = gql`
query ($bookId: ID!) {
book(id: $bookId) {
id
editions {
publisher
year
}
}
}
`
const bookReviewQuery = gql`
query ($bookId: ID!) {
book(id: $bookId) {
id
review {
user
score
date
}
}
}
`
All this queries will populate the same bucket in Apollo cache: book with id.
What is necessary to achieve: in react component BookDetails:
have 1 object with:
data.author
data.editions[0].year
data.review[0].user
Logically - this is one entry in cache.
Thank you for your help.
Almost nothing to save by using already fetched [and passed from parent] data ... only author ... all review and edition must be fetched, no cache usage at all.
... fetching review and editions by book resolver helps apollo cache to keep relation but also requires API to use additional ('book') resolver [level] while it is not required ... review and editions resolvers should be callable directly with book id ... and f.e. can be used by separate <Review /> sub component ... or review and editions called within one request using the same id parameter.
Just use data and dataDetails separately in component - avoid code complications, keep it simply readable:
const ShowDetails = ({data}) => {
const { loading, error, data:dataDetails } = useQuery(someQueryAsksAdditionalFileldsForEntryAlreadyPresentInCache);
}
if(loading) return "loading...";
return (
<div>
<div>author: {data.author}</div>
{dataDetails.review.map(...
... if you really want to join data
const ShowDetails = ({data}) => {
const [bookData, setBookData] = useState(null);
const { loading, error, data:dataDetails } = useQuery(someQueryAsksAdditionalFileldsForEntryAlreadyPresentInCache, {
onCompleted: (newData) => {
setBookData( {...data, ...newData } );
}
});
if(bookData) return ...
// {bookData.author}
// bookData.review.map(...
I'm using #apollo/react-hooks to poll data from a GraphQL query each 5mn.
My query accepts a list of parameters including the current date time like this:
const MyComponent = ({ query }) => {
const {loading, error, data} = useQuery(query, {
variables: {
variable1: valVariable1,
variable2: valVariable2,
variable3: valVariable3,
currentLocalDateTime: getCurrentLocalDateTime(),
},
pollInterval: 300000
});
if (loading) {
return <h1>Loading...</h1>;
};
if (error) {
return <h1>Error! {JSON.stringify(error)}</h1>;
}
return <div> Data: {JSON.stringify(data)}</div>;
});
Let's say I call my first query at "2020-03-06 08h:00mn", using the pollInterval defined above, my 2nd query will be called at 08h:05mn" and the 3rd one will be called at 08h:10mn"
The expected behaviour
When I look on my query on Chrome's Network after 12mn, I'll see 3 queries with different time variable values:
Query 1 : variables.currentLocalDateTime === "2020-03-06 08h:00mn"
Query 2 : variables.currentLocalDateTime === "2020-03-06 08h:05mn"
Query 3 :variables.currentLocalDateTime === "2020-03-06 08h:10mn"
The current behaviour
Query 1 : variables.currentLocalDateTime === "2020-03-06 08h:00mn"
Query 2 : variables.currentLocalDateTime === "2020-03-06 08h:00mn"
Query 3 : variables.currentLocalDateTime === "2020-03-06 08h:00mn"
What I tried to do
I tried to wait the onCompleted event from useQuery and then update variables but it doesn't help because it will be called only the 1st time (and I guess when new data come in but I'm not sure).
I tried to use useLazyQuery instead of useQuery and then use a setInterval() to update my variables then call the query again like this:
const MyComponent = ({ query }) => {
const [getMyData, { loading, data }] = useLazyQuery(query);
if (loading) {
return <h1>Loading...</h1>;
};
if (error) {
return <h1>Error! {JSON.stringify(error)}</h1>;
}
// Remove the previous created interval then:
setInterval(() => {
const newVariables = {...variables};
newVariables['currentLocalDateTime'] = getCurrentLocalDateTime();
getMyData({variables: newVariables});
}, 300000);
return <div> Data: {JSON.stringify(data)}</div>;
}
But it seems like I'm reimplementing the default Apollo polling using a setInterval in a "dirty way". So I'm not sure if it's a solution.
Any feedback or experience to update a React Apollo polling variables ?
It seems the issue is still open
https://github.com/apollographql/apollo-client/pull/4974
I found this thread where an user comments that we can use stopPolling and startPolling(POLL_INTERVAL) to update the query variables and it worked for me:
https://github.com/apollographql/apollo-client/issues/3053#issuecomment-503490308
This post is very helpful too: http://www.petecorey.com/blog/2019/09/23/apollo-quirks-polling-after-refetching-with-new-variables/
Here is an example:
const POLL_INTERVAL = 10_000;
const { refetch, stopPolling, startPolling } = useQuery(
MY_QUERY,
{
fetchPolicy: "network-only",
pollInterval: POLL_INTERVAL,
client
}
);
const onFilterChange = variables => {
stopPolling();
refetch({ ...variables });
startPolling(POLL_INTERVAL);
};
I have a component that I'd like to fetch data for a profile and allow posting messages on the same view. I've created a simple profile component here, and have the mutation set within the query, similar to the Apollo React tutorial.
When I run this, I get a properly rendered query with data. When the button is pressed, the mutation occurs but the page re-renders with an empty object in the Query's data parameter and the page errors with Cannot read property 'name' of undefined (which is expected given a blank data object).
Is there a better approach here?
const GET_PROFILE = gql`
query GetProfileQuery($profileId: ID!) {
getProfileInfo(profileId: $profileId) {
name
}
}
`;
const ADD_ENDORSEMENT = gql`
mutation AddEndorsement($profileId: ID!, $body: String!) {
addEndorsement(profileId: $profileId, body: $body) {
endorsementId
}
}
`;
const Profile = props => {
const profileId = props.match.params.profileId;
return (
<Query query={GET_PROFILE} variables={{ profileId: profileId }}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<Fragment>
<h1>Welcome {data.getProfileInfo.name}</h1>
<Mutation mutation={ADD_ENDORSEMENT}>
{(addEndorsement, { loading, error }) => (
<div>
<button
onClick={e => {
addEndorsement({
variables: {
profileId: profileId,
body: "This is a test."
}
});
}}
>
Save
</button>
{loading && <p>Loading...</p>}
{error && <p>Error :(</p>}
</div>
)}
</Mutation>
</Fragment>
);
}}
</Query>
);
};
I had a similar issue were data turned up undefined. In my case the problem was caused by a Query subscribeToMore option I was using invalidating the cache by silently failing to merge the updated data into the original Query results (it did not contain exactly identical data fields).
The question doesn't mention subscriptions, but it will be a similar cache invalidation problem.
To fix I'd first try making sure the Query and Mutation's return identical data.
If that doesn't resolve, then depending on responsiveness needs either look at the "refetchQueries/awaitRefetchQueries" to refresh the Query, or manually poking the updated data into the local cache with "update" options on the Mutation.
Good luck.
Try adding the following to the onClick mutation, it should help avoid needing to update the cache manually, although that would be the next step. You can pass just the string name of the query to refetchQueries, you don't need to pass the actual query there. Another trick is to ensure you are getting the same fields back so the cache object matches, (not sure what type you are returning on the query/mutation.
onClick={e => {
addEndorsement({
variables: {
profileId: profileId,
body: 'This is a test.',
},
refetchQueries: ['GetProfileQuery']
})
}}